{"openapi":"3.0.0","paths":{"/repository/org/{orgid}":{"get":{"operationId":"RepositoryController_getOrg","parameters":[{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"Organization id to read.","example":"acme-retail"},{"name":"enrich","in":"query","required":false,"description":"Resolve linked records inline.","schema":{"type":"boolean","default":false},"example":false}],"responses":{"200":{"description":"The organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an organization","description":"Fetches one organization by id. The org is a **path parameter**, not the `orgid` header, because this reads an org other than the caller's own — which is why it is root-only.\n\n#### Signature\n\n```http\nGET /repository/org/{orgid} (orgid: string, enrich?: boolean) -> The organization\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootUser`, `RootSystem`, `RootAdmin`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/org-by-hostname/{hostname}`","tags":["Repository · Organizations"]}},"/repository/org-by-hostname/{hostname}":{"get":{"operationId":"RepositoryController_getOrgIdByDomainName","parameters":[{"name":"hostname","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name to resolve.","example":"shop.example.com"},{"name":"enrich","in":"query","required":false,"schema":{"type":"boolean","default":false},"example":false}],"responses":{"200":{"description":"The organization owning that hostname","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an organization by hostname","description":"Resolves an organization from a domain name — how a multi-tenant front end works out which org a request belongs to before it has an `orgid` to send.\n\n#### Signature\n\n```http\nGET /repository/org-by-hostname/{hostname} (hostname: string, enrich?: boolean) -> The organization owning that hostname\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootUser`, `RootSystem`, `RootAdmin`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/org/{orgid}`","tags":["Repository · Organizations"]}},"/repository/org":{"get":{"operationId":"RepositoryController_queryOrg","parameters":[{"name":"keyword","in":"query","required":false,"description":"Search text.","schema":{"type":"string"},"example":"acme"},{"name":"fields","in":"query","required":false,"description":"Include full field detail rather than a summary.","schema":{"type":"boolean","default":false},"example":false}],"responses":{"200":{"description":"Matching organizations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Query organizations","description":"Lists organizations across the platform. **Root-level access only** — this crosses tenant boundaries and is not scoped to a single org.\n\n#### Signature\n\n```http\nGET /repository/org (keyword?: string, fields?: boolean) -> Matching organizations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootUser`, `RootSystem`, `RootAdmin`.\n\n#### Notes\n\n- Cross-tenant. Restrict who holds root roles accordingly.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/org/{orgid}`","tags":["Repository · Organizations"]}},"/repository/org/update/{orgid}":{"post":{"operationId":"RepositoryController_updateOrg","parameters":[{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"Organization to update.","example":"acme-retail"}],"responses":{"201":{"description":"The updated organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update an organization","description":"Updates an organization's settings. Root-level: it can target any org, not just the caller's.\n\n#### Signature\n\n```http\nPOST /repository/org/update/{orgid} (orgid: string, body) -> The updated organization\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/org/delete/{orgid}`","tags":["Repository · Organizations"],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Acme Retail (EMEA)"}}}}}},"/repository/org/create":{"post":{"operationId":"RepositoryController_createOrg","parameters":[],"responses":{"201":{"description":"The created organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create an organization","description":"Provisions a new organization — a tenant-level operation restricted to root roles.\n\n#### Signature\n\n```http\nPOST /repository/org/create (body) -> The created organization\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/org/update/{orgid}`","tags":["Repository · Organizations"],"requestBody":{"description":"The organization to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"acme-retail"},"title":{"type":"string","example":"Acme Retail"},"hostname":{"type":"string","example":"shop.example.com"}}},"example":{"name":"acme-retail","title":"Acme Retail","hostname":"shop.example.com"}}}}}},"/repository/org/delete/{orgid}":{"delete":{"operationId":"RepositoryController_deleteOrg","parameters":[{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"Organization to delete.","example":"acme-retail"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete an organization","description":"Deletes an entire organization. This is the most destructive operation on the platform — it removes a whole tenant — and is restricted to `RootAdmin` alone, a narrower role than any other endpoint here.\n\nThere is no confirmation step and no undo.\n\n#### Signature\n\n```http\nDELETE /repository/org/delete/{orgid} (orgid: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`.\n\n#### Notes\n\n- Irreversible, and removes an entire tenant's data. `RootAdmin` only.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/org/update/{orgid}`","tags":["Repository · Organizations"]}},"/repository/org/user/{email}":{"get":{"operationId":"RepositoryController_getUserOrg","parameters":[{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"The user's email address.","example":"ada@example.com"}],"responses":{"200":{"description":"Organizations the user belongs to","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a user's organizations","description":"Lists the organizations a user belongs to — how a sign-in flow offers an org picker to someone who is a member of several.\n\n**This route is public**, so it discloses which organizations a given email address belongs to without authentication. Rate-limit anything built on it.\n\n#### Signature\n\n```http\nGET /repository/org/user/{email} (email: string) -> Organizations the user belongs to\n```\n\n#### Access\n\nPublic — no credentials required. Required role(s): `User`.\n\n#### Notes\n\n- Unauthenticated — it confirms both that an account exists and where it has access.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/org/user/{email}:/{orgid}`","tags":["Repository · Organizations"]}},"/repository/org/query/{datatype}":{"post":{"operationId":"RepositoryController_queryOrgData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on.","example":"sf_product"}],"responses":{"201":{"description":"Matching records","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Query data across organizations","description":"Runs a query against a datatype at root level. Restricted to root roles because it is the cross-tenant form of the ordinary repository query.\n\n#### Signature\n\n```http\nPOST /repository/org/query/{datatype} (datatype: string, body) -> Matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootUser`, `RootPowerUser`, `RootSystem`.\n\n#### Notes\n\n- Root-scoped. Verify the tenant boundary before exposing results to a user.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/find/{datatype}`","tags":["Repository · Organizations"],"requestBody":{"description":"The query to run.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"query":{"data.status":"active"},"page":1,"pageSize":50}}}}}},"/repository/org/user/{email}:/{orgid}":{"delete":{"operationId":"RepositoryController_deleteUserOrg","parameters":[{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"The user's email address. A literal `:` must follow it in the URL.","example":"ada@example.com"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"Organization to remove them from.","example":"acme-retail"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Remove a user from an organization","description":"Removes a user's membership of an organization.\n\n**The route path is malformed.** It is declared as `org/user/:email:/:orgid` — with a stray colon after `:email` — so the URL requires a literal `:` between the email and the org id, giving `/repository/org/user/ada@example.com:/acme-retail`. That is almost certainly a typo for `org/user/:email/:orgid`; treat the endpoint as unstable and expect the path to change.\n\n#### Signature\n\n```http\nDELETE /repository/org/user/{email}:/{orgid} (email: string, orgid: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The literal `:` in the path is a defect in the route declaration, not a deliberate separator.\n- Unlike the other org routes, this one declares no role restriction.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/org/user/{email}`","tags":["Repository · Organizations"]}},"/repository/request-approval/{datatype}/{id}":{"get":{"operationId":"RepositoryController_requestApproval","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The approval request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Request approval for a record","description":"Submits a record into its approval workflow.\n\nNote this is mounted on `GET` despite changing state, so it can be triggered by anything that follows a link. Do not expose the URL where it may be prefetched.\n\n#### Signature\n\n```http\nGET /repository/request-approval/{datatype}/{id} (datatype: string, id: string) -> The approval request result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Notes\n\n- A state change on `GET` — and it needs only `read` permission, not `approve`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/approve/{datatype}/{id}`","tags":["Repository · Workflow"]}},"/repository/approve/{datatype}/{id}":{"post":{"operationId":"RepositoryController_approve","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The approved record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Approve a record","description":"Approves a record that is awaiting review, advancing it through its workflow. Requires the `approve` permission, which is separate from `update`.\n\n#### Signature\n\n```http\nPOST /repository/approve/{datatype}/{id} (datatype: string, id: string, body) -> The approved record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:approve`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/reject/{id}`","tags":["Repository · Workflow"],"requestBody":{"description":"Optional approval notes.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"notes":"Checked against the brand guidelines"}}}}}},"/repository/reject/{id}":{"post":{"operationId":"RepositoryController_reject","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The rejection result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Reject a record","description":"Rejects a record awaiting approval.\n\n**Known defect:** the handler reads a `datatype` path parameter, but the route declares only `{id}` — so `datatype` is always `undefined` here. The other workflow endpoints take `{datatype}/{id}`; this one does not, and behaves differently as a result. Verify the outcome rather than assuming symmetry with `approve`.\n\n#### Signature\n\n```http\nPOST /repository/reject/{id} (id: string, body) -> The rejection result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:approve`.\n\n#### Notes\n\n- Asymmetric with `approve`, which takes a datatype. The missing parameter is a defect, not a design choice.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/approve/{datatype}/{id}`","tags":["Repository · Workflow"],"requestBody":{"description":"Optional rejection notes.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"notes":"Copy does not match the approved messaging"}}}}}},"/repository/publish/{datatype}/{id}":{"post":{"operationId":"RepositoryController_publish","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The published record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Publish a record","description":"Makes a record publicly visible. Requires `create` permission rather than a dedicated publish right.\n\n#### Signature\n\n```http\nPOST /repository/publish/{datatype}/{id} (datatype: string, id: string) -> The published record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/unpublish/{datatype}/{id}`","tags":["Repository · Workflow"]}},"/repository/unpublish/{datatype}/{id}":{"post":{"operationId":"RepositoryController_unpublish","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The unpublished record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Unpublish a record","description":"Withdraws a record from public visibility without deleting it. Requires `delete` permission — a heavier right than publishing needs, so an account able to publish may be unable to reverse it.\n\n#### Signature\n\n```http\nPOST /repository/unpublish/{datatype}/{id} (datatype: string, id: string) -> The unpublished record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:delete`.\n\n#### Notes\n\n- Publishing needs `create`; unpublishing needs `delete`. The asymmetry is deliberate but surprising.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/publish/{datatype}/{id}`","tags":["Repository · Workflow"]}},"/repository/history/{datatype}/{id}":{"get":{"operationId":"RepositoryController_history","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The record's revisions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a record's history","description":"The revision history for a record — what changed, when and by whom. The basis for restoring an earlier version.\n\n#### Signature\n\n```http\nGET /repository/history/{datatype}/{id} (datatype: string, id: string) -> The record's revisions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/history/{restore}`","tags":["Repository · History"]}},"/repository/history/{restore}":{"post":{"operationId":"RepositoryController_historyRestore","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"restore","in":"path","required":true,"description":"Path segment required by the route; the body carries the actual instruction.","schema":{"type":"string"},"example":"restore"}],"responses":{"201":{"description":"The restored record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Restore a record revision","description":"Restores a record to an earlier revision.\n\nThe `{restore}` path segment is part of the route but the work is driven by the body — pass the record and the revision to restore there.\n\n#### Signature\n\n```http\nPOST /repository/history/{restore} (restore: string, body) -> The restored record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`, `content:update`.\n\n#### Notes\n\n- Restoring overwrites the current version — take the current state from `history` first if you may need it back.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/history/{datatype}/{id}`","tags":["Repository · History"],"requestBody":{"description":"Which record and revision to restore.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"datatype":"sf_product","id":"66f1a2b3c4d5e6f708192a3b","version":3}}}}}},"/repository/trash-restore":{"post":{"operationId":"RepositoryController_trashRestore","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Restore result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Restore records from trash","description":"Brings soft-deleted records back. Only works for records that were soft-deleted — a hard delete or a truncate leaves nothing to restore.\n\n#### Signature\n\n```http\nPOST /repository/trash-restore (body) -> Restore result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/delete/{datatype}/{id}`","tags":["Repository"],"requestBody":{"description":"What to restore.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"datatype":"sf_product","ids":["66f1a2b3c4d5e6f708192a3b"]}}}}}},"/repository/isunique/{datatype}/{attribute}/{value}/{scopeValue}":{"get":{"operationId":"RepositoryController_isUnique","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"attribute","required":true,"in":"path","schema":{"type":"string"},"description":"Field to check.","example":"email"},{"name":"value","required":true,"in":"path","schema":{"type":"string"},"description":"Value to test.","example":"ada@example.com"},{"name":"scopeValue","required":true,"in":"path","schema":{"type":"string"},"description":"Restrict uniqueness to a subset.","example":"acme-retail"}],"responses":{"200":{"description":"Whether the value is available","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"unique":{"type":"boolean","example":false}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Check whether a value is unique","description":"Reports whether a value is already taken for an attribute — the check behind \"this username is unavailable\" on a signup form.\n\nThis route is **public**: it can be called without authentication, which means it discloses whether a given email or username exists in the org. Rate-limit any public form built on it.\n\n`scopeValue` narrows the uniqueness check to a subset, for values that need only be unique within a group.\n\n#### Signature\n\n```http\nGET /repository/isunique/{datatype}/{attribute}/{value}/{scopeValue} (datatype: string, attribute: string, value: string, scopeValue: string) -> Whether the value is available\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated. It confirms the existence of accounts — treat it as an enumeration surface.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /repository/create`","tags":["Repository"]}},"/repository/collections/fix":{"post":{"operationId":"RepositoryController_fixCollection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was repaired","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Repair collection definitions","description":"Repairs inconsistencies in the org's collection definitions — an administrative maintenance operation, not part of normal use.\n\nIt rewrites schema metadata. Run it deliberately, not as part of a routine flow.\n\n#### Signature\n\n```http\nPOST /repository/collections/fix () -> What was repaired\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Maintenance operation — it modifies schema definitions across the org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/collections/{name}/{subName}`","tags":["Repository"]}},"/repository/collections/{name}/{subName}":{"get":{"operationId":"RepositoryController_getCollection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Collection name. Omit to list all.","example":"sf_product"},{"name":"subName","required":true,"in":"path","schema":{"type":"string"},"description":"Nested collection name.","example":"variant"},{"name":"enriched","in":"query","required":false,"description":"Include resolved schema detail. **Defaults to true**, unlike most enrich flags on this controller.","schema":{"type":"boolean","default":true},"example":true},{"name":"collectionType","in":"query","required":false,"description":"Filter by collection type.","schema":{"type":"string"},"example":"content"}],"responses":{"200":{"description":"Collection definitions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get collection definitions","description":"Returns the collection (datatype) definitions for the org — the schemas that describe what fields each datatype has. Omit `name` to list them all.\n\nThis is how a generic client discovers what it can create: read the collection definition, then build a form from its schema.\n\n#### Signature\n\n```http\nGET /repository/collections/{name}/{subName} (name: string, subName: string, enriched?: boolean, collectionType?: string) -> Collection definitions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `enriched` defaults to `true` here; the `en` flag elsewhere on this controller defaults to false.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/collections/fix`","tags":["Repository"]}},"/repository/link/{datatype}/{id}":{"get":{"operationId":"RepositoryController_recordLink","summary":"A studio link to a record","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"`{ url, title, datatype }`","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"response not found — No record of that datatype has the given id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"response not found","path":"/repository/link/{datatype}/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"description":"A link to a record in the studio, to hand to a person — \"here it is\" instead of \"the id is 6a5b…\". Returns `{ url, title, datatype }`; the url opens `/app/collection/{datatype}/{id}`, which shows the record in its own app when it has one, else in the Database app. The title is the person's name, else the record's title, subject, name or email.\n\n#### Signature\n\n```http\nGET /repository/link/{datatype}/{id} (datatype: string, id: string) -> `{ url, title, datatype }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Staff only: a customer token gets `403`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | response not found | No record of that datatype has the given id. | Check the id and datatype. Note the message is the literal string `response not found`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/get/{datatype}/{id}`","tags":["Repository"]}},"/repository/get/{datatype}/{id}":{"get":{"operationId":"RepositoryController_getData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"response not found — No record of that datatype has the given id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"response not found","path":"/repository/get/{datatype}/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a record by id","description":"Fetches one record by its `sk`.\n\n**Mind the overload.** This route and `GET /repository/get/{datatype}/{attribute}/{value}` share the same `get/` prefix and are told apart only by how many segments follow. Three segments (`get/product/abc123`) is a lookup by id; four (`get/product/name/widget`) is a lookup by attribute. Getting the arity wrong silently runs the other query.\n\n#### Signature\n\n```http\nGET /repository/get/{datatype}/{id} (datatype: string, id: string) -> The record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This is one of the few repository reads that raises a real `404` rather than returning null.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | response not found | No record of that datatype has the given id. | Check the id and datatype. Note the message is the literal string `response not found`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/find-any-id/{datatype}/{id}`\n- `GET /repository/get/{datatype}/{attribute}/{value}`","tags":["Repository"]}},"/repository/find-any-id/{datatype}/{id}":{"get":{"operationId":"RepositoryController_findOneAnyId","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Any identifier — `sk`, `name`, or another natural key.","example":"cola-330ml"},{"name":"en","required":false,"in":"query","schema":{"type":"boolean"},"description":"Resolve linked records inline instead of returning bare references. Costs extra queries.","example":false}],"responses":{"200":{"description":"The record, or `null` when nothing matches","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a record by any identifier","description":"Resolves a record from whatever identifier you have — the `sk`, the `name`, or another natural key such as a SKU. Use this when the caller holds a slug from a URL rather than a record id.\n\nA record that does not exist comes back as `null` with a `200`, not a `404`.\n\n#### Signature\n\n```http\nGET /repository/find-any-id/{datatype}/{id} (datatype: string, id: string, en?: boolean) -> The record, or `null` when nothing matches\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns `null` + `200` for a miss — check the body, not the status.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/get/{datatype}/{id}`","tags":["Repository"]}},"/repository/get/{datatype}/{attribute}/{value}":{"get":{"operationId":"RepositoryController_findByDatatypeAttribute2","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"attribute","required":true,"in":"path","schema":{"type":"string"},"description":"Field to match, **without** a `data.` prefix.","example":"brand"},{"name":"value","required":true,"in":"path","schema":{"type":"string"},"description":"Value to match.","example":"fizzco"},{"name":"l","required":false,"in":"query","schema":{"type":"string"},"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth."},{"name":"en","required":false,"in":"query","schema":{"type":"boolean"},"description":"Resolve linked records inline instead of returning bare references. Costs extra queries.","example":false},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"}],"responses":{"200":{"description":"A page of matching records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}},"total":{"type":"integer","example":42}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Find records by attribute","description":"Lists records of a datatype whose attribute equals a value, paged.\n\nThe attribute is resolved **within the data payload**, so pass the bare field name — `name`, not `data.name`. Prefixing it with `data.` produces a double prefix and matches nothing.\n\nOmitting both attribute and value lists the datatype unfiltered. See the note on `GET /repository/get/{datatype}/{id}` about how these two routes are distinguished.\n\n#### Signature\n\n```http\nGET /repository/get/{datatype}/{attribute}/{value} (datatype: string, attribute: string, value: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string, en?: boolean) -> A page of matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Notes\n\n- Attributes are scoped to the data payload automatically — `data.name` will not match.\n- Matching is exact equality, not a partial or fuzzy match. Use `search` for that.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/find-by-attribute/{datatype}/{attribute}/{attrValue}`\n- `GET /repository/search/{datatype}`","tags":["Repository"]}},"/repository/find-by-attribute/{datatype}/{attribute}/{attrValue}":{"get":{"operationId":"RepositoryController_findByDatatypeAttribute","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"attribute","required":true,"in":"path","schema":{"type":"string"},"description":"Field to match, without a `data.` prefix.","example":"brand"},{"name":"attrValue","required":true,"in":"path","schema":{"type":"string"},"description":"Value to match.","example":"fizzco"},{"name":"l","required":false,"in":"query","schema":{"type":"string"},"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth."},{"name":"keyword","required":false,"in":"query","schema":{"type":"string"},"description":"Text filter applied alongside the attribute match.","example":"cola"},{"name":"en","required":false,"in":"query","schema":{"type":"boolean"},"description":"Resolve linked records inline instead of returning bare references. Costs extra queries.","example":false},{"name":"attributes","in":"query","required":false,"description":"Additional field/value pairs to match, as a map.","schema":{"type":"string"},"example":"status=active"},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"}],"responses":{"200":{"description":"A page of matching records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}},"total":{"type":"integer","example":42}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Find records by attribute","description":"The explicit form of the attribute lookup, without the arity overload of the `get/` routes — prefer this one in new work.\n\nIt additionally accepts an `attributes` map for matching several fields at once, and a `keyword` for a text filter alongside the attribute match.\n\nAs with the `get/` form, attributes are resolved inside the data payload: pass `name`, not `data.name`.\n\n#### Signature\n\n```http\nGET /repository/find-by-attribute/{datatype}/{attribute}/{attrValue} (datatype: string, attribute: string, attrValue: string, attributes?: string, keyword?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string, en?: boolean) -> A page of matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Notes\n\n- Preferred over `GET /repository/get/{datatype}/{attribute}/{value}` — same behaviour, unambiguous route.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/get/{datatype}/{attribute}/{value}`","tags":["Repository"]}},"/repository/lookup-code":{"post":{"operationId":"RepositoryController_lookupCode","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What the code resolved to","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"The kind of record matched.","example":"sf_order"},"match":{"type":"object","additionalProperties":true,"description":"The record itself."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Resolve a scanned code","description":"Resolves a scanned string — a barcode, receipt number, ticket code — to the record it identifies, returning `{ type, match }`.\n\nIt answers *what this code is*, not what to do about it: routing the user to the right screen is the client's decision. Narrow the search with `types` when the scanner's context already limits what a code could be.\n\nThis route is **public** — anyone can probe codes against it.\n\n#### Signature\n\n```http\nPOST /repository/lookup-code (body) -> What the code resolved to\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — do not rely on it to keep record existence private.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /repository/find-any-id/{datatype}/{id}`","tags":["Repository"],"requestBody":{"description":"The scanned code.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"The scanned string.","example":"9400111899223197428490"},"types":{"type":"array","items":{"type":"string"},"description":"Restrict which record types to try. Omit to try all.","example":["sf_order","ticket"]}}},"examples":{"any":{"summary":"Resolve against everything","value":{"code":"A7K2M9QX4"}},"scoped":{"summary":"Only orders and tickets","value":{"code":"A7K2M9QX4","types":["sf_order","ticket"]}}}}}}}},"/repository/find-timed-data/{startDate}/{endDate}":{"get":{"operationId":"RepositoryController_findTimedData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":true,"in":"path","schema":{"type":"string"},"description":"Start of the range.","example":"2026-08-01"},{"name":"endDate","required":true,"in":"path","schema":{"type":"string"},"description":"End of the range.","example":"2026-08-31"},{"name":"enrich","required":false,"in":"query","schema":{"type":"boolean"},"description":"Resolve linked records inline.","example":false},{"name":"datatypes","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated collections to search. Omit to search the defaults.","example":"sf_order,booking"}],"responses":{"200":{"description":"Records in the range","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Find records in a date range","description":"Returns records falling inside a date range, across one or more datatypes. The read behind a calendar or an activity feed that spans several collections at once.\n\nBoth dates are optional path segments; `datatypes` narrows which collections are searched.\n\n#### Signature\n\n```http\nGET /repository/find-timed-data/{startDate}/{endDate} (startDate: string, endDate: string, datatypes?: string, enrich?: boolean) -> Records in the range\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Notes\n\n- Omitting both dates returns an unbounded range — always supply at least one on a large org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/search/{datatype}`","tags":["Repository"]}},"/repository/find-page-data/{datatype}/{dataId}":{"get":{"operationId":"RepositoryController_query","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on.","example":"sf_product"},{"name":"dataId","required":true,"in":"path","schema":{"type":"string"},"description":"Restrict to one record.","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"l","required":false,"in":"query","schema":{"type":"string"},"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth."},{"name":"keyword","required":false,"in":"query","schema":{"type":"string"},"example":"cola"},{"name":"en","required":true,"in":"query","schema":{"type":"boolean"}},{"name":"attributes","in":"query","required":false,"schema":{"type":"string"},"example":"status=active"},{"name":"categories","in":"query","required":false,"schema":{"type":"string"},"example":"cold-drinks"},{"name":"tags","in":"query","required":false,"schema":{"type":"string"},"example":"summer-sale"},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"}],"responses":{"200":{"description":"A page of matching records","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Query records (alias)","description":"An alias for `GET /repository/find/{datatype}/{dataId}` — the same handler is mounted at both paths. Prefer `find/`.\n\n#### Signature\n\n```http\nGET /repository/find-page-data/{datatype}/{dataId} (datatype: string, dataId: string, attributes?: string, categories?: string, tags?: string, keyword?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string) -> A page of matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/find/{datatype}/{dataId}`","tags":["Repository"]}},"/repository/find/{datatype}/{dataId}":{"get":{"operationId":"RepositoryController_query","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on.","example":"sf_product"},{"name":"dataId","required":true,"in":"path","schema":{"type":"string"},"description":"Restrict to one record.","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"l","required":false,"in":"query","schema":{"type":"string"},"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth."},{"name":"keyword","required":false,"in":"query","schema":{"type":"string"},"description":"Text filter.","example":"cola"},{"name":"en","required":false,"in":"query","schema":{"type":"boolean"},"description":"Resolve linked records inline.","example":false},{"name":"attributes","in":"query","required":false,"description":"Field/value pairs to match.","schema":{"type":"string"},"example":"status=active"},{"name":"categories","in":"query","required":false,"description":"Categories to match. Repeat for several.","schema":{"type":"string"},"example":"cold-drinks"},{"name":"tags","in":"query","required":false,"description":"Tags to match. Repeat for several.","schema":{"type":"string"},"example":"summer-sale"},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"}],"responses":{"200":{"description":"A page of matching records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}},"total":{"type":"integer","example":42}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Query records","description":"The richest read on the controller: filters by attributes, categories, tags and keyword together, with paging and sorting.\n\nIt is mounted at two paths — `find/...` and `find-page-data/...` — which behave identically. `find/` is the one to use; the other name is historical.\n\n#### Signature\n\n```http\nGET /repository/find/{datatype}/{dataId} (datatype: string, dataId: string, attributes?: string, categories?: string, tags?: string, keyword?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string, en?: boolean) -> A page of matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/find-page-data/{datatype}/{dataId}`\n- `POST /repository/find/{datatype}`","tags":["Repository"]}},"/repository/category/{datatype}":{"get":{"operationId":"RepositoryController_getDataTypeCategories","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"Collection to read categories for. Omit for all.","example":"sf_product"}],"responses":{"200":{"description":"Categories","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get categories for a datatype","description":"The categories in use for a datatype. Omit `datatype` for categories across everything.\n\n#### Signature\n\n```http\nGET /repository/category/{datatype} (datatype: string) -> Categories\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/tag`","tags":["Repository"]}},"/repository/tag":{"get":{"operationId":"RepositoryController_getTags","summary":"List tags","description":"The tags defined for the org, optionally filtered by keyword — the source for a tag autocomplete.\n\n#### Signature\n\n```http\nGET /repository/tag (keyword?: string) -> Tags\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/tag`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"keyword","required":false,"in":"query","description":"Filter tags by text.","schema":{"type":"string"},"example":"sum"}],"responses":{"200":{"description":"Tags","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Repository"]},"post":{"operationId":"RepositoryController_addTag","summary":"Create a tag","description":"Adds a tag to the org's tag vocabulary so it can be applied to records and offered in autocomplete.\n\n#### Signature\n\n```http\nPOST /repository/tag (body) -> The created tag\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/tag`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The tag to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"summer-sale"},"title":{"type":"string","example":"Summer Sale"}}},"example":{"name":"summer-sale","title":"Summer Sale"}}}},"responses":{"201":{"description":"The created tag","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Repository"]}},"/repository/find-related/{datatype}/{anyId}":{"get":{"operationId":"RepositoryController_findRelated","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on.","example":"sf_product"},{"name":"anyId","required":true,"in":"path","schema":{"type":"string"},"description":"Any identifier for the source record.","example":"cola-330ml"}],"responses":{"200":{"description":"Related records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Find related records","description":"Returns records linked to a given one, across datatypes. Resolves by any identifier, not just the `sk`.\n\n#### Signature\n\n```http\nGET /repository/find-related/{datatype}/{anyId} (datatype: string, anyId: string) -> Related records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/find/{datatype}/{dataId}`","tags":["Repository"]}},"/repository/find/{datatype}":{"post":{"operationId":"RepositoryController_findPost","summary":"Find records of any data type — look up a user (staff, colleague) by email or name and get their phone, or customers, orders, anything","description":"Look up any data the caller can read: `datatype` is the collection. The org’s people are `user` (staff — name in `data.firstName`/`data.lastName`, `data.title`, `data.email`, `data.phone`); customers are `customer`. Body `{ query, options }`: `query` is a filter on the record (fields under `data.`), `options` `{ page, pageSize, sort }`. Example — a colleague’s phone number: `POST /repository/find/user` with `{ \"query\": { \"data.email\": \"someone@company.com\" } }`. Also mounted at `POST /repository/query/{datatype}`.\n\n#### Signature\n\n```http\nPOST /repository/find/{datatype} (datatype: string, body) -> Matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/query/{datatype}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on.","example":"sf_product"}],"responses":{"201":{"description":"Matching records","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Repository"],"requestBody":{"description":"The filter and paging.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"query":{"data.email":"someone@company.com"},"options":{"page":1,"pageSize":20}}}}}}},"/repository/query/{datatype}":{"post":{"operationId":"RepositoryController_findPost","summary":"Query records with a body (alias)","description":"An alias for `POST /repository/find/{datatype}` — the same handler on both paths.\n\n#### Signature\n\n```http\nPOST /repository/query/{datatype} (datatype: string, body) -> Matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/find/{datatype}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on.","example":"sf_product"}],"responses":{"201":{"description":"Matching records","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Repository"],"requestBody":{"description":"The query to run.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"query":{"data.status":"active"},"options":{"page":1,"pageSize":50}}}}}}},"/repository/find-asset/{datatype}":{"post":{"operationId":"RepositoryController_findAsset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","in":"path","required":true,"description":"The collection to act on.","schema":{"type":"string"},"example":"sf_product"}],"responses":{"201":{"description":"Matching assets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Find assets","description":"Searches assets of a datatype using a body-supplied query.\n\n#### Signature\n\n```http\nPOST /repository/find-asset/{datatype} (datatype: string, body) -> Matching assets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/search-asset/{datatype}`","tags":["Repository · Assets"],"requestBody":{"description":"The asset query.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"keyword":"hero","options":{"page":1,"pageSize":50}}}}}}},"/repository/find-experts":{"get":{"operationId":"RepositoryController_findExperts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"keyword","required":true,"in":"query","schema":{"type":"string"},"description":"Search text.","example":"photography"}],"responses":{"200":{"description":"Matching experts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Find experts","description":"Searches for experts by keyword — a domain-specific lookup that happens to live on the repository controller.\n\n#### Signature\n\n```http\nGET /repository/find-experts (keyword?: string) -> Matching experts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Repository"]}},"/repository/update-asset/{datatype}/{id}":{"post":{"operationId":"RepositoryController_updateAsset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","in":"path","required":true,"description":"The collection to act on.","schema":{"type":"string"},"example":"sf_product"},{"name":"id","in":"path","required":true,"description":"Asset id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated asset","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update an asset","description":"Updates an asset's metadata.\n\n#### Signature\n\n```http\nPOST /repository/update-asset/{datatype}/{id} (datatype: string, id: string, body) -> The updated asset\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootUser`, `RootSystem`, `RootAdmin`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/delete-asset/{datatype}`","tags":["Repository · Assets"],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Cola hero image","alt":"A chilled can of cola"}}}}}},"/repository/delete-asset/{datatype}":{"post":{"operationId":"RepositoryController_deleteAsset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","in":"path","required":true,"description":"The collection to act on.","schema":{"type":"string"},"example":"sf_product"}],"requestBody":{"description":"Asset ids to delete, as a bare array.","required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}},"example":["66f1a2b3c4d5e6f708192a3b"]}}},"responses":{"201":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete assets","description":"Deletes several assets at once. The body is a bare array of ids.\n\n#### Signature\n\n```http\nPOST /repository/delete-asset/{datatype} (datatype: string, body) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootUser`, `RootSystem`, `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Deletes the asset records. Whether the underlying files are removed depends on the storage driver.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/delete`","tags":["Repository · Assets"]}},"/repository/search/{datatype}":{"get":{"operationId":"RepositoryController_searchGet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"keyword","required":false,"in":"query","schema":{"type":"string"},"description":"Text to search for.","example":"cola"},{"name":"query","required":false,"in":"query","schema":{"type":"string"},"description":"Structured filter as a JSON string. Parsed leniently — malformed JSON is ignored.","example":"{\"data.status\":\"active\"}"},{"name":"datatype","in":"path","required":true,"description":"Collection to search. Omit to search across all.","schema":{"type":"string"},"example":"sf_product"},{"name":"p","in":"query","required":false,"schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"schema":{"type":"integer","default":50},"example":50}],"responses":{"200":{"description":"Matching records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}},"total":{"type":"integer","example":12}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search records","description":"Full-text search across a datatype, with an optional structured `query` for additional filtering. Omit `datatype` to search across everything.\n\n`query` is a JSON string parsed leniently — a malformed value is treated as absent rather than rejected, so a syntax error silently widens the search instead of failing.\n\n#### Signature\n\n```http\nGET /repository/search/{datatype} (datatype: string, keyword?: string, query?: string, p?: integer, ps?: integer) -> Matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Notes\n\n- A malformed `query` is silently dropped. Check the result count if a filter appears to have no effect.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/search/{datatype}`","tags":["Repository"]},"post":{"operationId":"RepositoryController_searchPost","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","in":"path","required":true,"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","schema":{"type":"string"},"example":"sf_product"}],"responses":{"201":{"description":"Matching records","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search records with a body","description":"The POST form of search, for queries too large or too structured to sit in a query string. `query` is a real object here rather than a JSON string, so it is not silently dropped when malformed.\n\n#### Signature\n\n```http\nPOST /repository/search/{datatype} (datatype: string, body) -> Matching records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Notes\n\n- Prefer this over the GET form for anything beyond a simple keyword — the structured query is validated rather than silently ignored.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/search/{datatype}`","tags":["Repository"],"requestBody":{"description":"The search to run.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"keyword":{"type":"string","description":"Text to search for.","example":"cola"},"query":{"type":"object","additionalProperties":true,"description":"Structured filter."},"options":{"type":"object","additionalProperties":true,"description":"Paging and sorting.","properties":{"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50}}}}},"example":{"keyword":"cola","query":{"data.status":"active"},"options":{"page":1,"pageSize":50}}}}}}},"/repository/search-asset/{datatype}":{"post":{"operationId":"RepositoryController_searchAsset","parameters":[{"name":"datatype","in":"path","required":true,"description":"The collection to act on.","schema":{"type":"string"},"example":"sf_product"}],"responses":{"201":{"description":"Matching assets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search assets","description":"Full-text search across assets of a datatype.\n\n**Note this handler does not read the `orgid` header** — unlike every other asset endpoint, it is not org-scoped at the controller level.\n\n#### Signature\n\n```http\nPOST /repository/search-asset/{datatype} (datatype: string, body) -> Matching assets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Takes no `orgid` — verify the scoping before relying on it in a multi-tenant context.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/find-asset/{datatype}`","tags":["Repository · Assets"],"requestBody":{"description":"The search to run.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"keyword":{"type":"string","example":"hero"},"query":{"type":"object","additionalProperties":true},"options":{"type":"object","additionalProperties":true}}},"example":{"keyword":"hero"}}}}}},"/repository/update/{id}":{"post":{"operationId":"RepositoryController_updateHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update a record","description":"Replaces a record's data. The datatype is inferred from the record itself, so it is not a path parameter here — unlike almost every other write on this controller.\n\nUse `update-partial` to change a few fields without sending the whole payload.\n\n#### Signature\n\n```http\nPOST /repository/update/{id} (id: string, body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:update`.\n\n#### Notes\n\n- This writes the payload you send — fields you omit can be lost. Prefer `update-partial` for targeted edits.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/update-partial/{datatype}/{id}`","tags":["Repository"],"requestBody":{"description":"The record to write.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"data":{"sku":"DRK-COLA-330","title":"Cola 330ml","price":12.5}}}}}}},"/repository/update-partial/{datatype}/{id}":{"post":{"operationId":"RepositoryController_updatePartialHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Partially update a record","description":"Writes only the fields you send, leaving the rest of the record untouched. This is the safe way to edit one part of a large record, and the way to avoid two concurrent editors overwriting each other's unrelated fields.\n\nPaths are dotted and absolute within the record — `data.children`, not `children`.\n\n#### Signature\n\n```http\nPOST /repository/update-partial/{datatype}/{id} (datatype: string, id: string, body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:update`.\n\n#### Notes\n\n- Paths include the `data.` prefix here, unlike the attribute-lookup endpoints which strip it.\n- Send dotted paths, never a nested `data` object. `{\"data\": {...}}` REPLACES the entire `data` field — every key you did not include, such as images, price or attributes, is dropped. Snapshot the record with `find` before any bulk write.\n- Send the `version` you read from `get`. A stale version is rejected instead of silently overwriting a concurrent editor.\n- Read with `get/{datatype}/{id}` before editing. `find` returns a partial projection, so a record edited from a `find` result can lose fields you never saw.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/update/{id}`","tags":["Repository"],"requestBody":{"description":"The fields to write, keyed by dotted path.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Dotted paths to values."},"examples":{"singleField":{"summary":"Change one field","value":{"data.price":12.5}},"nested":{"summary":"Write a nested array","value":{"data.children":[{"name":"cold-drinks"}]}},"withVersion":{"summary":"Pin the write to the version you read","value":{"sk":"6aa7…","version":11,"data.price":12.5}},"destructive":{"summary":"DO NOT do this — replaces the whole data object","value":{"data":{"price":12.5}}}}}}}}},"/repository/setting/{settingType}/{settingName}/{subName}":{"get":{"operationId":"RepositoryController_getSettingHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"settingType","required":true,"in":"path","schema":{"type":"string"},"description":"Setting group.","example":"securitySettings"},{"name":"settingName","required":true,"in":"path","schema":{"type":"string"},"description":"Named setting within the group.","example":"passwordPolicy"},{"name":"subName","required":true,"in":"path","schema":{"type":"string"},"description":"Nested value within the setting.","example":"minLength"}],"responses":{"200":{"description":"The setting value","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an org setting","description":"Reads an organization setting. `settingType` names the group; `settingName` and `subName` drill into named or nested values.\n\nSettings can hold credentials and security configuration, which is why these routes are admin-only.\n\n#### Signature\n\n```http\nGET /repository/setting/{settingType}/{settingName}/{subName} (settingType: string, settingName: string, subName: string) -> The setting value\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `ContentAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/setting/{settingType}/{settingName}/{subName}`","tags":["Repository · Settings"]},"post":{"operationId":"RepositoryController_setSettingHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"settingType","required":true,"in":"path","schema":{"type":"string"},"description":"Setting group.","example":"securitySettings"},{"name":"settingName","required":true,"in":"path","schema":{"type":"string"},"description":"Named setting within the group.","example":"passwordPolicy"},{"name":"subName","required":true,"in":"path","schema":{"type":"string"},"description":"Nested value within the setting.","example":"minLength"}],"responses":{"201":{"description":"The stored setting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Set an org setting","description":"Writes an organization setting.\n\nThe body is unwrapped intelligently: send `{ \"value\": … }` and the inner value is stored; send anything else and the whole body is stored as the value. That means a payload that happens to contain a `value` key will be unwrapped whether you meant it or not.\n\n#### Signature\n\n```http\nPOST /repository/setting/{settingType}/{settingName}/{subName} (settingType: string, settingName: string, subName: string, body) -> The stored setting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `ContentAdmin`.\n\n#### Notes\n\n- A body containing a `value` key is always unwrapped — wrap it twice if `value` is a genuine field of your setting.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/setting/{settingType}/{settingName}/{subName}`","tags":["Repository · Settings"],"requestBody":{"description":"The value to store. Wrapped in `value`, or sent bare.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"value":{"description":"When present, this inner value is stored rather than the whole body."}}},"examples":{"wrapped":{"summary":"Explicit value wrapper","value":{"value":{"minLength":12}}},"bare":{"summary":"Bare object stored as-is","value":{"minLength":12,"requireSymbol":true}}}}}}},"delete":{"operationId":"RepositoryController_deleteSettingHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"settingType","required":true,"in":"path","schema":{"type":"string"},"description":"Setting group.","example":"securitySettings"},{"name":"settingName","required":true,"in":"path","schema":{"type":"string"},"description":"Named setting. Omit to delete the whole group.","example":"passwordPolicy"},{"name":"subName","required":true,"in":"path","schema":{"type":"string"},"description":"Nested value.","example":"minLength"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete an org setting","description":"Removes an organization setting. Deleting a group rather than a named setting removes everything under it, so name the setting precisely.\n\n#### Signature\n\n```http\nDELETE /repository/setting/{settingType}/{settingName}/{subName} (settingType: string, settingName: string, subName: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `ContentAdmin`.\n\n#### Notes\n\n- Omitting `settingName` deletes the entire group.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/setting/{settingType}/{settingName}/{subName}`","tags":["Repository · Settings"]}},"/repository/create-extended":{"put":{"operationId":"RepositoryController_createPageWit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The created record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a record with extensions","description":"Creates a record along with its related sub-records in one call — the composite form of create, for a record that is not useful on its own.\n\n#### Signature\n\n```http\nPUT /repository/create-extended (body) -> The created record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/create`","tags":["Repository"],"requestBody":{"description":"The record and its extensions.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"datatype":"sf_product","data":{"sku":"DRK-COLA-330"},"extensions":[]}}}}}},"/repository/create":{"post":{"operationId":"RepositoryController_createHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The record to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["datatype","data","isNew"],"properties":{"datatype":{"type":"string","description":"Collection to create in.","example":"sf_product"},"data":{"type":"object","additionalProperties":true,"description":"Domain fields for that datatype."},"isNew":{"type":"boolean","description":"Required, and must be `true`. Omit it and the call fails with 400 \"create => record is not marked as new\". The flag is named `isNew` — not `new`.","example":true}}},"example":{"datatype":"sf_product","isNew":true,"data":{"sku":"DRK-COLA-330","title":"Cola 330ml","price":12}}}}},"responses":{"200":{"description":"The result of the create operation","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"message":{"type":"string"}}}}}},"201":{"description":"The created record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a record","description":"Creates a record of any datatype. `datatype` names the collection and `data` carries the domain fields.\n\nThe same handler is mounted on both `POST` and `PUT` — they behave identically.\n\n#### Signature\n\n```http\nPOST /repository/create (body) -> The created record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.\n\n#### Notes\n\n- `isNew: true` is mandatory. Without it the request is rejected with 400 — the record is only accepted as a creation when it says it is new.\n- Author is taken from the authenticated user, not the body.\n- Field validation depends on the datatype's schema — an unknown datatype is created without one.\n- A top-level `name` is truncated at 100 characters; the full title belongs in `data`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /repository/create`\n- `POST /repository/update/{id}`","tags":["Repository"]},"put":{"operationId":"RepositoryController_createPutHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The created record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a record","description":"Identical to `POST /repository/create` — the same handler is mounted on both verbs. Use whichever your client prefers.\n\n#### Signature\n\n```http\nPUT /repository/create (body) -> The created record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/create`","tags":["Repository"],"requestBody":{"description":"The record to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["datatype","data","isNew"],"properties":{"datatype":{"type":"string","example":"sf_product"},"data":{"type":"object","additionalProperties":true},"isNew":{"type":"boolean","description":"Required, and must be `true`. See `POST /repository/create`.","example":true}}},"example":{"datatype":"sf_product","isNew":true,"data":{"sku":"DRK-COLA-330","title":"Cola 330ml","price":12}}}}}}},"/repository/clone":{"put":{"operationId":"RepositoryController_cloneHandler","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"An unsaved copy of the record","content":{"application/json":{"schema":{"type":"object","description":"A platform record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Domain fields — shape depends on `datatype`."}}}}}},"400":{"description":"Clone failed — The source record does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Clone failed","path":"/repository/clone","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Clone a record","description":"Builds a copy of a record and **returns it without saving**. The clone exists only in the response — post it to `POST /repository/create` to persist it.\n\nThat makes it a template-builder rather than a duplicate operation: adjust the returned copy before creating it.\n\n#### Signature\n\n```http\nPUT /repository/clone (body) -> An unsaved copy of the record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.\n\n#### Notes\n\n- **Nothing is persisted.** The clone is discarded unless you create it explicitly.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CLONE_FAILED | Clone failed | The source record does not exist. | Check `datatype` and `uid`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/create`","tags":["Repository"],"requestBody":{"description":"Which record to clone.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["datatype","uid"],"properties":{"datatype":{"type":"string","example":"sf_product"},"uid":{"type":"string","description":"Record `sk` to copy.","example":"66f1a2b3c4d5e6f708192a3b"}}},"example":{"datatype":"sf_product","uid":"66f1a2b3c4d5e6f708192a3b"}}}}}},"/repository/delete/{datatype}/{id}":{"delete":{"operationId":"RepositoryController_deleteData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"'<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. — The datatype is `customer` or `user`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"'<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal.","path":"/repository/delete/{datatype}/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can delete a root group. Blocked: RootAdmin. — A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can delete a root group. Blocked: RootAdmin.","path":"/repository/delete/{datatype}/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a record","description":"Deletes one record.\n\n**`customer` and `user` are refused here.** Deleting an account has to notify the person, unwind their sessions and leave an audit trail, and a generic row delete does none of that — the account holder would find out when they next tried to sign in. The error names the domain endpoint to use instead.\n\n#### Signature\n\n```http\nDELETE /repository/delete/{datatype}/{id} (datatype: string, id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:delete`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | USE_DOMAIN_API | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. | The datatype is `customer` or `user`. | Use the domain endpoint the error names — `DELETE user/customer/profile/{emailOrUsername}` or `DELETE user/user/delete/{emailOrUsername}`. The error body carries `code`, `datatype` and `endpoint`. |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can delete a root group. Blocked: RootAdmin. | A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. | Root groups are managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/delete/{datatype}`\n- `POST /repository/trash-restore`","tags":["Repository"]}},"/repository/delete/{datatype}":{"post":{"operationId":"RepositoryController_deleteBulkData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"}],"requestBody":{"description":"The record ids to delete, as a bare array.","required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"Record `sk` values."},"example":["66f1a2b3c4d5e6f708192a3b","66f1a2b3c4d5e6f708192a3c"]}}},"responses":{"201":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"'<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. — The datatype is `customer` or `user`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"'<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal.","path":"/repository/delete/{datatype}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can delete a root group. Blocked: RootAdmin. — A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can delete a root group. Blocked: RootAdmin.","path":"/repository/delete/{datatype}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete several records","description":"Deletes many records of one datatype in a single call. The same `customer` / `user` guard applies as for the single delete.\n\n#### Signature\n\n```http\nPOST /repository/delete/{datatype} (datatype: string, body) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:delete`.\n\n#### Notes\n\n- The body is a bare array of ids, not an object wrapping one.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | USE_DOMAIN_API | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. | The datatype is `customer` or `user`. | Use the domain endpoint the error names — `DELETE user/customer/profile/{emailOrUsername}` or `DELETE user/user/delete/{emailOrUsername}`. The error body carries `code`, `datatype` and `endpoint`. |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can delete a root group. Blocked: RootAdmin. | A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. | Root groups are managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/delete/{datatype}/{id}`","tags":["Repository"]}},"/repository/delete-query/{datatype}":{"post":{"operationId":"RepositoryController_deleteByQuery","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"}],"responses":{"201":{"description":"{ matched, deleted, failed }","content":{"application/json":{"schema":{"type":"object","properties":{"matched":{"type":"integer"},"deleted":{"type":"integer"},"failed":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"matched":12,"deleted":12,"failed":[]}}}},"400":{"description":"'<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. — The datatype is `customer` or `user`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"'<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal.","path":"/repository/delete-query/{datatype}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can delete a root group. Blocked: RootAdmin. — A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can delete a root group. Blocked: RootAdmin.","path":"/repository/delete-query/{datatype}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete every record a query matches","description":"The list screen's \"Select all N → Delete\": finds every record of the datatype matching `query` (a Mongo filter on the stored shape, e.g. `{ \"data.status\": \"archived\" }`) and deletes each one the same way the bulk delete does — cascades, trash, the `customer` / `user` refusal and the root-group guard. Answers with the counts.\n\n**An empty or missing `query` matches every record of the datatype** — it is then equivalent to a truncate.\n\n#### Signature\n\n```http\nPOST /repository/delete-query/{datatype} (datatype: string, body) -> { matched, deleted, failed }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:delete`.\n\n#### Notes\n\n- Send a real filter. `{}` deletes the whole collection.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | USE_DOMAIN_API | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. | The datatype is `customer` or `user`. | Use the domain endpoint the error names — `DELETE user/customer/profile/{emailOrUsername}` or `DELETE user/user/delete/{emailOrUsername}`. The error body carries `code`, `datatype` and `endpoint`. |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can delete a root group. Blocked: RootAdmin. | A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. | Root groups are managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/delete/{datatype}`\n- `DELETE /repository/truncate/{datatype}`","tags":["Repository"],"requestBody":{"description":"The filter.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"object","additionalProperties":true}}},"example":{"query":{"data.status":"archived"}}}}}}},"/repository/truncate/{datatype}":{"delete":{"operationId":"RepositoryController_truncateCollection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","example":"sf_product"}],"responses":{"200":{"description":"Truncation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This datatype is removed through its own API — The datatype (e.g. customer, user) has a domain delete endpoint.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This datatype is removed through its own API","path":"/repository/truncate/{datatype}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Truncate a collection","description":"Deletes **every record** of a datatype in the organization. There is no filter and no undo.\n\nRoot administrators only, and API only: the Studio no longer offers it. Datatypes that must be removed through their own domain API (such as `customer` and `user`) are refused with `USE_DOMAIN_API`, the same rule the single and bulk deletes apply. Every call is logged with who made it.\n\n#### Signature\n\n```http\nDELETE /repository/truncate/{datatype} (datatype: string) -> Truncation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`.\n\n#### Notes\n\n- Irreversible and unfiltered — it empties the whole collection for the org.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | USE_DOMAIN_API | This datatype is removed through its own API | The datatype (e.g. customer, user) has a domain delete endpoint. | Use that endpoint instead. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/delete/{datatype}`","tags":["Repository"]}},"/repository/aggregate/{datatype}":{"post":{"operationId":"RepositoryController_aggregate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","in":"path","required":true,"description":"The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`.","schema":{"type":"string"},"example":"sf_product"}],"responses":{"201":{"description":"The aggregation result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Run an aggregation","description":"Runs an aggregation pipeline over a datatype and returns the result — the escape hatch for reporting queries the list endpoints cannot express.\n\nThe body is passed to the storage engine, so it is powerful and unvalidated: an expensive pipeline runs exactly as written. Scope aggregations tightly on large collections.\n\n#### Signature\n\n```http\nPOST /repository/aggregate/{datatype} (datatype: string, body) -> The aggregation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.\n\n#### Notes\n\n- Unvalidated and unbounded — an unscoped pipeline can scan an entire collection.\n- Results are org-scoped, but the pipeline itself is otherwise passed through as given.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/search/{datatype}`","tags":["Repository"],"requestBody":{"description":"The aggregation pipeline.","required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Pipeline stages."},"example":[{"$match":{"data.status":"active"}},{"$group":{"_id":"$data.brand","count":{"$sum":1}}}]}}}}},"/repository/file/append":{"post":{"operationId":"RepositoryController_append","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/append","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Append to a file","description":"Appends content to the end of an existing file, without reading and rewriting the whole thing.\n\n#### Signature\n\n```http\nPOST /repository/file/append (body) -> The updated file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/prepend`","tags":["Repository · Files"],"requestBody":{"description":"What to append, and where.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"},"content":{"type":"string","description":"Content to append."}}},"example":{"location":"logs/import.log","content":"row 412 imported\n"}}}}}},"/repository/file/prepend":{"post":{"operationId":"RepositoryController_prepend","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/prepend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Prepend to a file","description":"Inserts content at the start of an existing file.\n\n#### Signature\n\n```http\nPOST /repository/file/prepend (body) -> The updated file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/append`","tags":["Repository · Files"],"requestBody":{"description":"What to prepend, and where.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"},"content":{"type":"string","description":"Content to prepend."}}},"example":{"location":"logs/import.log","content":"# import started\n"}}}}}},"/repository/file/copy":{"post":{"operationId":"RepositoryController_copy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The copied file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/copy","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Copy a file","description":"Copies a file to another location, leaving the original in place.\n\n#### Signature\n\n```http\nPOST /repository/file/copy (body) -> The copied file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/move`","tags":["Repository · Files"],"requestBody":{"description":"Source and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"from":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"},"to":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"from":"products/cola.jpg","to":"archive/cola.jpg"}}}}}},"/repository/file/move":{"post":{"operationId":"RepositoryController_move","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The moved file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/move","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Move a file","description":"Moves a file to another location. Any URL already pointing at the old path stops resolving, so update references before moving a file that is in use.\n\n#### Signature\n\n```http\nPOST /repository/file/move (body) -> The moved file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Breaks existing links to the old path.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/copy`","tags":["Repository · Files"],"requestBody":{"description":"Source and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"from":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"},"to":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"from":"products/cola.jpg","to":"archive/cola.jpg"}}}}}},"/repository/file/delete":{"post":{"operationId":"RepositoryController_delete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/delete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a file","description":"Deletes a file from storage. Permanent — there is no trash for files as there is for records.\n\n#### Signature\n\n```http\nPOST /repository/file/delete (body) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Irreversible. Records still referencing the file keep a URL that no longer resolves.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/exists`","tags":["Repository · Files"],"requestBody":{"description":"The file to delete.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/driver":{"get":{"operationId":"RepositoryController_driver","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The active storage driver","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get the storage driver","description":"Reports which storage backend the org uses. Useful when behaviour differs between drivers — folder semantics and signed-URL support in particular.\n\n#### Signature\n\n```http\nGET /repository/file/driver () -> The active storage driver\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Repository · Files"]}},"/repository/file/exists":{"post":{"operationId":"RepositoryController_exists","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the file exists","content":{"application/json":{"schema":{"type":"object","properties":{"exists":{"type":"boolean","example":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Check whether a file exists","description":"Reports whether a file is present at a location, without fetching it.\n\n#### Signature\n\n```http\nPOST /repository/file/exists (body) -> Whether the file exists\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/stat`","tags":["Repository · Files"],"requestBody":{"description":"The location to check.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file":{"post":{"operationId":"RepositoryController_getFile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The stored file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Write a file","description":"Writes file content directly, without a multipart upload — for generated content such as an exported CSV or a rendered template.\n\n#### Signature\n\n```http\nPOST /repository/file (body) -> The stored file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/append`","tags":["Repository · Files"],"requestBody":{"description":"The file to write.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"},"content":{"type":"string","description":"File content."},"metadata":{"type":"object","additionalProperties":true}}},"example":{"location":"exports/report.csv","content":"sku,title\nDRK-COLA-330,Cola 330ml"}}}}}},"/repository/file/create_favicon":{"post":{"operationId":"RepositoryController_createFavicon","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The generated favicons","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/create_favicon","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a favicon","description":"Generates `favicon.ico` and `favicon.png` at the org bucket root from a source image, and returns `{ ico: { signedUrl, path }, png: { signedUrl, path } }`.\n\nGive it a **square** source, at least 512×512, with the mark filling the frame. The generator resizes but does not crop, so a wide wordmark becomes illegible at 16×16.\n\n#### Signature\n\n```http\nPOST /repository/file/create_favicon (body) -> The generated favicons\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `location` must NOT include the org prefix. An upload returns `<org>/brand/logo.png`; pass `brand/logo.png`. Passing the full path scopes it twice and the source is not found.\n- Generating the files is not enough to change the icon a browser shows. Write the URL to the site record as `site.data.favicon`, and it MUST be a plain string — an object is silently ignored and the site keeps the platform default. (`site.data.logo` may be an object; the two are not symmetrical.)\n- Page-level `<link rel=\"icon\">` tags in page HTML are deliberately stripped by the renderer, so a page cannot set its own favicon. The site record is the only mechanism.\n- The generated files are private — an unsigned GET returns 403. Use the returned signed URLs.\n- Every generated `.ico` is the same byte length, because it is a multi-resolution container. Compare hashes, not sizes, to tell two apart.\n- Verify on the rendered page, not the record: `curl -s https://your-site/ | grep -oE '<link rel=\"icon\" href=\"[^\"]{0,80}'`. Seeing `/favicon.ico` means your value was ignored.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/thumbnails`\n- `POST /repository/file/upload`","tags":["Repository · Files"],"requestBody":{"description":"The source image, as a path WITHOUT the org prefix.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"brand/logo.png"}}}}}},"/repository/file/get_asset":{"post":{"operationId":"RepositoryController_getFileAsset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The asset","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/get_asset","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an asset file","description":"Retrieves a file as an asset record, with its metadata alongside the content.\n\n#### Signature\n\n```http\nPOST /repository/file/get_asset (body) -> The asset\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/stat`","tags":["Repository · Files"],"requestBody":{"description":"The asset to fetch.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/buffer":{"post":{"operationId":"RepositoryController_getFileBuffer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The file contents","content":{"application/json":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/buffer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get file contents as a buffer","description":"Returns the raw bytes of a file. Use `signurl` instead for anything large — this loads the whole file into memory.\n\n#### Signature\n\n```http\nPOST /repository/file/buffer (body) -> The file contents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Loads the entire file. Prefer a signed URL for large files.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/signurl`\n- `POST /repository/file/stream`","tags":["Repository · Files"],"requestBody":{"description":"The file to read.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/signurl":{"post":{"operationId":"RepositoryController_getSignedUrl","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The signed URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Time-limited access URL."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/signurl","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a signed URL for a file","description":"Returns a time-limited URL that grants direct access to a private file, so a browser can fetch it without the request passing through the platform.\n\nThe URL carries its own authorization — anyone holding it has access until it expires. Do not log or cache it.\n\n#### Signature\n\n```http\nPOST /repository/file/signurl (body) -> The signed URL\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The URL is a bearer credential. Treat it as a secret for its lifetime.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/make-private`","tags":["Repository · Files"],"requestBody":{"description":"The file to sign.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"},"expiresIn":{"type":"integer","description":"Lifetime in seconds.","example":3600}}},"example":{"location":"private/contract.pdf","expiresIn":3600}}}}}},"/repository/file/stat":{"post":{"operationId":"RepositoryController_getStat","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"File metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/stat","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get file metadata","description":"Returns size, content type and timestamps for a file without downloading it.\n\n#### Signature\n\n```http\nPOST /repository/file/stat (body) -> File metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/exists`","tags":["Repository · Files"],"requestBody":{"description":"The file to inspect.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/index":{"post":{"operationId":"RepositoryController_indexFileLocation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Indexing result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Index files","description":"Rebuilds the searchable index over stored files. An administrative operation — run it after a bulk import that bypassed normal upload.\n\n#### Signature\n\n```http\nPOST /repository/file/index (body) -> Indexing result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Repository · Files"],"requestBody":{"description":"What to index.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/repository/file/stream":{"post":{"operationId":"RepositoryController_getStream","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The file stream","content":{"application/json":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/stream","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Stream a file","description":"Streams a file rather than buffering it — the right choice for large files and for passing content straight through to a client.\n\n#### Signature\n\n```http\nPOST /repository/file/stream (body) -> The file stream\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/buffer`","tags":["Repository · Files"],"requestBody":{"description":"The file to stream.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/url":{"post":{"operationId":"RepositoryController_getUrl","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The file URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a file URL","description":"Returns the public URL for a file. For a private file, use `signurl` instead — a public URL will not resolve.\n\n#### Signature\n\n```http\nPOST /repository/file/url (body) -> The file URL\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/signurl`","tags":["Repository · Files"],"requestBody":{"description":"The file to resolve.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/make-private":{"post":{"operationId":"RepositoryController_makePrivate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/make-private","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Make a file private","description":"Removes public access from a file. Existing public URLs stop resolving, and access then requires a signed URL.\n\n#### Signature\n\n```http\nPOST /repository/file/make-private (body) -> The updated file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Already-issued signed URLs keep working until they expire.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/make-public`","tags":["Repository · Files"],"requestBody":{"description":"The file to restrict.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/make-public":{"post":{"operationId":"RepositoryController_makePublic","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/make-public","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Make a file public","description":"Grants public access to a file. **Anyone with the URL can then read it, with no authentication** — check the contents before making a file public.\n\n#### Signature\n\n```http\nPOST /repository/file/make-public (body) -> The updated file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Irreversible in effect once the URL has been shared, even if you make the file private again later.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/make-private`","tags":["Repository · Files"],"requestBody":{"description":"The file to publish.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/cola-330ml.jpg"}}}}}},"/repository/file/createfolder":{"post":{"operationId":"RepositoryController_createfolder","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created folder","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a folder","description":"Creates a folder in storage. Most drivers infer folders from file paths, so this is only needed where an empty folder must exist.\n\n#### Signature\n\n```http\nPOST /repository/file/createfolder (body) -> The created folder\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/flatlist`","tags":["Repository · Files"],"requestBody":{"description":"The folder to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"products/2026"}}}}}},"/repository/file/thumbnails":{"post":{"operationId":"RepositoryController_createThumbnails","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The generated thumbnails","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"File not found — No file exists at that location.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"File not found","path":"/repository/file/thumbnails","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate thumbnails","description":"Generates thumbnail renditions for an image.\n\n#### Signature\n\n```http\nPOST /repository/file/thumbnails (body) -> The generated thumbnails\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FILE_NOT_FOUND | File not found | No file exists at that location. | Check the path with `POST /repository/file/exists` first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/create_favicon`","tags":["Repository · Files"],"requestBody":{"description":"The image to generate thumbnails for.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"},"sizes":{"type":"array","items":{"type":"integer"},"description":"Widths to generate.","example":[200,800]}}},"example":{"location":"products/cola.jpg","sizes":[200,800]}}}}}},"/repository/file/upload":{"post":{"operationId":"RepositoryController_upload","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The stored file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Upload a file","description":"Uploads a file as multipart form data. The stored path is `location` plus the original filename.\n\n**`isPrivate` is read as the string `\"true\"`, not a boolean** — because this is a multipart form, a JSON `true` does not match and the file is stored **public**. Send the literal string.\n\n#### Signature\n\n```http\nPOST /repository/file/upload (body) -> The stored file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A boolean `true` for `isPrivate` does not work — only the string `\"true\"` makes the file private.\n- Uploading to an existing path overwrites it without warning.\n- `location` is a FOLDER, not a full path. The filename is appended for you — passing `products/chair/chair.jpg` stores `products/chair/chair.jpg/chair.jpg`.\n- The returned `path` is org-prefixed (`<org>/<location>/<file>`). Endpoints that take a `location` want the path WITHOUT that prefix.\n- Files are served as `application/octet-stream`. Browsers sniff raster formats, so PNG and JPEG render normally — **SVG does not**, and must be inlined into the page instead of linked.\n- Raise your client timeout for large files; the default in most HTTP clients is too short for a slow connection.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/upload-url`\n- `POST /repository/file/make-private`\n- `POST /repository/file/create_favicon`","tags":["Repository · Files"],"requestBody":{"description":"Multipart form: the file plus its placement.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The file to upload."},"location":{"type":"string","description":"Directory to place it in. The original filename is appended.","example":"products"},"metadata":{"type":"object","additionalProperties":true,"description":"Metadata stored with the file."},"isPrivate":{"type":"string","enum":["true","false"],"description":"Send the **string** `\"true\"` to store privately. Anything else stores it public.","example":"false"}}}}}}}},"/repository/file/upload-url":{"post":{"operationId":"RepositoryController_uploadFromUrl","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The stored file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Upload a file from a URL","description":"Fetches a file from a URL and stores it, without the client having to download and re-upload it.\n\n#### Signature\n\n```http\nPOST /repository/file/upload-url (body) -> The stored file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The server fetches the URL — it must be reachable from the server, not just from your client.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/upload`","tags":["Repository · Files"],"requestBody":{"description":"Where to fetch from and where to store it.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","description":"Source URL.","example":"https://example.com/hero.jpg"},"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"url":"https://example.com/hero.jpg","location":"products/hero.jpg"}}}}}},"/repository/file/flatlist/{prefix}/{pageNumber}":{"get":{"operationId":"RepositoryController_flatList","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"prefix","in":"path","required":true,"description":"Path prefix to list under.","schema":{"type":"string"},"example":"products"},{"name":"pageNumber","in":"path","required":true,"description":"Page number.","schema":{"type":"string"},"example":1},{"name":"check-privacy","in":"query","required":false,"description":"Also report each file's public/private state. Slower.","schema":{"type":"boolean","default":false},"example":false}],"responses":{"200":{"description":"Files under the prefix","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List files (URL form)","description":"The URL form of the flat file listing. `check-privacy` additionally reports whether each file is public or private, at the cost of extra lookups.\n\n#### Signature\n\n```http\nGET /repository/file/flatlist/{prefix}/{pageNumber} (prefix: string, pageNumber: string, check-privacy?: boolean) -> Files under the prefix\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/file/flatlist`","tags":["Repository · Files"]}},"/repository/file/flatlist":{"post":{"operationId":"RepositoryController_flatListPost","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Files under the prefix","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List files","description":"Lists files under a prefix as a flat list rather than a tree.\n\n#### Signature\n\n```http\nPOST /repository/file/flatlist (body) -> Files under the prefix\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/file/flatlist/{prefix}/{pageNumber}`","tags":["Repository · Files"],"requestBody":{"description":"What to list.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"prefix":{"type":"string","description":"Path prefix to list under.","example":"products"},"pageNumber":{"type":"integer","example":1}}},"example":{"prefix":"products","pageNumber":1}}}}}},"/repository/media/photos":{"post":{"operationId":"RepositoryController_searchPhotosFromPexel","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Matching stock photos","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search stock photos","description":"Searches a stock photo provider, so an editor can pick an image without leaving the product. Results are the provider's and are not stored until used.\n\n#### Signature\n\n```http\nPOST /repository/media/photos (body) -> Matching stock photos\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Provider licensing applies to any image you use — this endpoint does not grant rights.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/media/photos/curated`","tags":["Repository · Media"],"requestBody":{"description":"The photo search.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"Search text.","example":"summer drinks"},"per_page":{"type":"integer","example":20},"page":{"type":"integer","example":1},"orientation":{"type":"string","enum":["landscape","portrait","square"],"example":"landscape"},"size":{"type":"string","example":"large"},"color":{"type":"string","example":"blue"},"locale":{"type":"string","example":"en-US"}}},"example":{"query":"summer drinks","per_page":20,"orientation":"landscape"}}}}}},"/repository/media/photos/curated":{"post":{"operationId":"RepositoryController_searchPhotosFromPexelCurated","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Curated photos","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get curated stock photos","description":"Returns the provider's curated photo selection, for a starting point when there is no search term.\n\n#### Signature\n\n```http\nPOST /repository/media/photos/curated (body) -> Curated photos\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/media/photos`","tags":["Repository · Media"],"requestBody":{"description":"Paging.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"per_page":{"type":"integer","example":20},"page":{"type":"integer","example":1}}},"example":{"per_page":20,"page":1}}}}}},"/repository/media/videos":{"post":{"operationId":"RepositoryController_searchVideosFromPexel","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Matching stock videos","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search stock videos","description":"Searches a stock video provider. The video counterpart to the photo search.\n\n#### Signature\n\n```http\nPOST /repository/media/videos (body) -> Matching stock videos\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/media/videos/popular`","tags":["Repository · Media"],"requestBody":{"description":"The video search.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"query":{"type":"string","example":"city timelapse"},"per_page":{"type":"integer","example":20},"page":{"type":"integer","example":1}}},"example":{"query":"city timelapse","per_page":20}}}}}},"/repository/media/videos/popular":{"post":{"operationId":"RepositoryController_searchVideosFromPexelPopular","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Popular videos","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get popular stock videos","description":"Returns the provider's popular video selection.\n\n#### Signature\n\n```http\nPOST /repository/media/videos/popular (body) -> Popular videos\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/media/videos`","tags":["Repository · Media"],"requestBody":{"description":"Paging.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"per_page":{"type":"integer","example":20},"page":{"type":"integer","example":1}}},"example":{"per_page":20}}}}}},"/repository/customer/file/flatlist":{"post":{"operationId":"RepositoryController_customerFlatList","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The customer's files","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List customer files","description":"Lists files in the calling customer's own storage area.\n\n#### Signature\n\n```http\nPOST /repository/customer/file/flatlist (body) -> The customer's files\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/customer/file/upload`","tags":["Repository · Files"],"requestBody":{"description":"What to list.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"prefix":{"type":"string","example":"documents"}}},"example":{"prefix":"documents"}}}}}},"/repository/customer/file/upload":{"post":{"operationId":"RepositoryController_customerUpload","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The stored file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string","example":"https://cdn.appmint.io/acme/products/cola-330ml.jpg"},"path":{"type":"string","example":"products/cola-330ml.jpg"},"contentType":{"type":"string","example":"image/jpeg"},"size":{"type":"integer","description":"Bytes.","example":84213},"isPrivate":{"type":"boolean","example":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Upload a customer file","description":"Uploads a file into the calling customer's own storage area, kept separate from org files so a customer cannot reach another's uploads.\n\n#### Signature\n\n```http\nPOST /repository/customer/file/upload (body) -> The stored file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/customer/file/flatlist`","tags":["Repository · Files"],"requestBody":{"description":"Multipart form with the file.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"location":{"type":"string","example":"documents"}}}}}}}},"/repository/customer/file/delete":{"post":{"operationId":"RepositoryController_customerDelete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a customer file","description":"Deletes a file from the calling customer's storage area.\n\n#### Signature\n\n```http\nPOST /repository/customer/file/delete (body) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/customer/file/flatlist`","tags":["Repository · Files"],"requestBody":{"description":"The file to delete.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["location"],"properties":{"location":{"type":"string","description":"Path within the org's storage.","example":"products/cola-330ml.jpg"}}},"example":{"location":"documents/contract.pdf"}}}}}},"/repository/preview/{orgId}/{site}/{page}":{"get":{"operationId":"RepositoryController_previewPage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"orgId","required":true,"in":"path","schema":{"type":"string"},"description":"Organization id, in the path rather than the header.","example":"acme-retail"},{"name":"site","in":"path","required":true,"description":"Site name.","schema":{"type":"string"},"example":"main-store"},{"name":"page","in":"path","required":true,"description":"Page name.","schema":{"type":"string"},"example":"home"}],"responses":{"200":{"description":"The rendered preview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Preview a page","description":"Renders a preview of a site page. Note the org is a **path parameter** here rather than the `orgid` header, because a preview link has to carry everything it needs.\n\n#### Signature\n\n```http\nGET /repository/preview/{orgId}/{site}/{page} (orgId: string, site: string, page: string) -> The rendered preview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The only repository route that takes the org from the path.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Repository"]}},"/repository/bulk-create":{"post":{"operationId":"RepositoryController_bulkCreate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The creation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create many records","description":"Creates many records in one call — the import path for a data load. Records are of one datatype and validated as a set.\n\n#### Signature\n\n```http\nPOST /repository/bulk-create (body) -> The creation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent — re-running an import duplicates the records unless the datatype enforces uniqueness.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/migrate`","tags":["Repository"],"requestBody":{"description":"The records to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"datatype":{"type":"string","example":"sf_product"},"records":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"datatype":"sf_product","records":[{"sku":"DRK-COLA-330","title":"Cola 330ml"}]}}}}}},"/repository/export/{datatype}":{"post":{"operationId":"RepositoryController_exportData","summary":"Export records","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The collection to act on.","example":"sf_product"}],"requestBody":{"description":"What to export, and in what format.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"format":{"type":"string","enum":["csv","json"],"default":"csv","description":"Anything other than `json` is treated as `csv`.","example":"csv"},"query":{"type":"object","additionalProperties":true,"description":"Filter for the records to export."},"fields":{"type":"array","items":{"type":"string"},"description":"Columns to include."}}},"examples":{"csv":{"summary":"CSV export of active products","value":{"format":"csv","query":{"data.status":"active"}}},"json":{"summary":"JSON export","value":{"format":"json"}}}}}},"responses":{"201":{"description":"The exported file, streamed as an attachment named `<datatype>-<date>.<format>`","content":{"text/csv":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"description":"Exports a datatype as CSV or JSON, streamed as a file download.\n\n**A mid-export failure still returns `200`.** Because the row count is unknown until the last page is read, the response is chunked and the headers are sent before the data — so once an export starts, there is no way to signal failure through the status code. Instead the error is written **into the file**: a CSV gains a trailing `# export failed: …` line, and a JSON export is closed off with `]`.\n\n**Always check the tail of the file** before trusting an export to be complete.\n\nCSV output begins with a UTF-8 BOM so Excel reads non-ASCII characters correctly rather than mangling them as the local codepage.\n\n#### Signature\n\n```http\nPOST /repository/export/{datatype} (datatype: string, body) -> The exported file, streamed as an attachment named `<datatype>-<date>.<format>`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A failed export returns `200` with a truncated file — check the last line for `# export failed:` (CSV) or a bare `]` (JSON).\n- CSV output carries a UTF-8 BOM, which some strict parsers will need told about.\n- The response is chunked, so `Content-Length` is absent and progress cannot be measured.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/bulk-create`","tags":["Repository"]}},"/repository/migrate":{"post":{"operationId":"RepositoryController_migrateData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The migration to run.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sources","targetDatatype"],"properties":{"sources":{"type":"array","description":"Where records come from. Several sources can feed one target.","items":{"type":"object","required":["datatype"],"properties":{"datatype":{"type":"string","description":"Source collection.","example":"legacy_product"},"query":{"type":"object","additionalProperties":true,"description":"Filter selecting which records to migrate. Omit to take everything."},"fieldMap":{"type":"object","additionalProperties":true,"description":"Maps source field names to target field names."}}}},"targetDatatype":{"type":"string","description":"Destination collection.","example":"sf_product"},"targetCollection":{"type":"object","description":"Definition for the target collection, created if it does not exist.","properties":{"name":{"type":"string"},"title":{"type":"string"},"schema":{"type":"object","additionalProperties":true}}},"mode":{"type":"string","enum":["instant","batch","auto"],"description":"`instant` runs now, `batch` queues a job, `auto` decides.","example":"auto"},"deleteFromSource":{"type":"boolean","default":false,"description":"**Destructive.** Removes records from the source after migrating them.","example":false}}},"examples":{"dryish":{"summary":"Copy without deleting","value":{"sources":[{"datatype":"legacy_product","fieldMap":{"title":"name"}}],"targetDatatype":"sf_product","mode":"auto"}},"move":{"summary":"Move and delete from source","value":{"sources":[{"datatype":"legacy_product"}],"targetDatatype":"sf_product","mode":"batch","deleteFromSource":true}}}}}},"responses":{"201":{"description":"The migration result, or a job id when batched","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Migrate data between collections","description":"Moves or copies records from one or more source collections into a target collection, optionally mapping fields on the way.\n\n`mode` controls how it runs: `instant` performs it synchronously, `batch` queues it as a job, and `auto` chooses based on volume. A batched migration returns a job id to poll.\n\n**`deleteFromSource` makes this destructive.** With it set, records are removed from the source once migrated — run without it first and check the result before repeating with deletion enabled.\n\n#### Signature\n\n```http\nPOST /repository/migrate (body) -> The migration result, or a job id when batched\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Notes\n\n- Run once without `deleteFromSource` and verify the target before running again with it.\n- A `batch` migration returns a job id — poll `GET /repository/migrate/status/{jobId}` rather than assuming completion.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/migrate/status/{jobId}`\n- `POST /repository/migrate/cancel/{jobId}`","tags":["Repository · Migration"]}},"/repository/migrate/status/{jobId}":{"get":{"operationId":"RepositoryController_getMigrationStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Migration job id, from the migrate response.","example":"MIG-4821"}],"responses":{"200":{"description":"The job status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get migration job status","description":"Reports progress for a batched migration — how far it has got and whether it succeeded.\n\n#### Signature\n\n```http\nGET /repository/migrate/status/{jobId} (jobId: string) -> The job status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/migrate/jobs`","tags":["Repository · Migration"]}},"/repository/migrate/jobs":{"get":{"operationId":"RepositoryController_listMigrationJobs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by job status.","example":"running"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Migration jobs","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List migration jobs","description":"Lists migration jobs with optional status filtering and paging — the history of what has been migrated.\n\n#### Signature\n\n```http\nGET /repository/migrate/jobs (status?: string, page?: integer, pageSize?: integer) -> Migration jobs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/migrate/status/{jobId}`","tags":["Repository · Migration"]}},"/repository/migrate/cancel/{jobId}":{"post":{"operationId":"RepositoryController_cancelMigrationJob","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Migration job id.","example":"MIG-4821"}],"responses":{"201":{"description":"The cancellation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Cancel a migration job","description":"Stops a running migration.\n\nCancelling does not roll back what has already been migrated — records already written to the target stay there, and with `deleteFromSource` set, records already removed stay removed. Check the job status to see how far it got.\n\n#### Signature\n\n```http\nPOST /repository/migrate/cancel/{jobId} (jobId: string) -> The cancellation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- No rollback. A cancelled migration leaves partially-migrated data behind.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /repository/migrate/status/{jobId}`","tags":["Repository · Migration"]}},"/site/get-org/{domainName}":{"get":{"operationId":"SiteController_getOrgByDomainName","summary":"Resolve a domain to an organization","description":"Maps a hostname back to the org that owns it. This is the first hop in serving a request for a custom domain — before a page can be rendered, the tenant has to be identified from the host header.\n\n#### Signature\n\n```http\nGET /site/get-org/{domainName} (domainName: string) -> The owning organization\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/get-site-by-hostname/{hostname}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"domainName","required":true,"in":"path","description":"Hostname.","schema":{"type":"string"},"example":"shop.example.com"}],"responses":{"200":{"description":"The owning organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/company/{domainName}":{"get":{"operationId":"SiteController_getPublicCompany","summary":"Get the public company profile for a domain","description":"The org behind a domain (or behind the `orgid` header when the domain segment is left off), reduced to what a site may publish about itself: `{ name, email, displayName }`. An empty object when the org has none of them — never an error. Tax id, legal name and entity type sit on the same record and are never returned.\n\n#### Signature\n\n```http\nGET /site/company/{domainName} (domainName: string) -> Public company profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/get-org/{domainName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"domainName","required":true,"in":"path","description":"Hostname or org id. Optional — without it the `orgid` header is used.","schema":{"type":"string"},"example":"shop.example.com"}],"responses":{"200":{"description":"Public company profile","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"displayName":{"type":"string"}}},"example":{"name":"acme","email":"hello@acme.com","displayName":"Acme Coffee"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/get-site/{configSiteName}/{domainName}":{"get":{"operationId":"SiteController_getSite","summary":"Get site information","description":"Fetches a site by its configured name, optionally narrowed by domain. `domainName` is an optional path segment — the route also matches without it.\n\n#### Signature\n\n```http\nGET /site/get-site/{configSiteName}/{domainName} (configSiteName: string, domainName: string) -> The site\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The fields on `site.data` that most often trip people up: `homePage` matches the home page record's **`name`**, not its slug — a page named `home` with slug `index` is NOT found by `homePage: \"index\"`, and the site serves its fallback at `/` with no error.\n- `data.favicon` must be a plain STRING url. An object is silently ignored and the site keeps the platform default — no icon link is emitted at all. `data.logo` may be an object (`{path,url}`) because it is normalised; the two fields are not symmetrical.\n- `hostName` is the platform host and always current; `domain` is the custom domain and sits behind a cache. Verify a deploy on `hostName` first — if it shows the change and the custom domain does not, that is the cache, and redeploying will not help.\n- `seo.noIndex` is the site-wide kill switch and outranks any per-page value; it is the same flag robots.txt reads.\n- Set `hideSiteHeader` and `hideSiteFooter` when your pages carry their own chrome, or the page renders two headers.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_OR_DOMAIN_REQUIRED | Either siteName or domainName is required | Neither identifier resolves to anything. | Supply a valid site name. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/get-site-by-hostname/{hostname}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"configSiteName","required":true,"in":"path","description":"Configured site name.","schema":{"type":"string"},"example":"acme-shop"},{"name":"domainName","required":true,"in":"path","description":"Optional hostname.","schema":{"type":"string"},"example":"shop.example.com"},{"name":"shared-host","required":false,"in":"query","description":"Shared host","schema":{}}],"responses":{"200":{"description":"The site","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Either siteName or domainName is required — Neither identifier resolves to anything.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Either siteName or domainName is required","path":"/site/get-site/{configSiteName}/{domainName}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/get-site-by-hostname/{hostname}":{"get":{"operationId":"SiteController_getSiteByDomainName","summary":"Get a site by hostname","description":"Resolves a hostname straight to its site configuration — the lookup a renderer does per request. The hostname segment is optional in the route, but omitting it leaves nothing to resolve.\n\n#### Signature\n\n```http\nGET /site/get-site-by-hostname/{hostname} (hostname: string) -> The site\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_OR_DOMAIN_REQUIRED | Either siteName or domainName is required | No hostname was supplied. | Pass the hostname in the path. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/get-org/{domainName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"hostname","required":true,"in":"path","schema":{"type":"string"},"description":"Hostname.","example":"shop.example.com"},{"name":"shared-host","required":false,"in":"query","description":"Shared host","schema":{}},{"name":"domainName","required":false,"in":"path","description":"Domain name","schema":{}},{"name":"configSiteName","required":true,"in":"path","description":"Site configuration name","schema":{}}],"responses":{"200":{"description":"The site","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Either siteName or domainName is required — No hostname was supplied.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Either siteName or domainName is required","path":"/site/get-site-by-hostname/{hostname}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/get-site-host-mappings/{siteName}":{"get":{"operationId":"SiteController_getSiteHostMappings","summary":"Get a site's host mappings","description":"Every hostname routed to a site — the default platform domain plus any custom domains. This is the source of truth for what actually reaches the site; a domain configured at the registrar but absent here does not resolve.\n\n#### Signature\n\n```http\nGET /site/get-site-host-mappings/{siteName} (siteName: string, keyword?: string, fields?: string) -> Host mappings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/fix-domains`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"siteName","required":true,"in":"path","schema":{"type":"string"},"description":"Site name.","example":"acme-shop"},{"name":"keyword","required":false,"in":"query","schema":{"type":"string"},"description":"Filter mappings.","example":"example.com"},{"name":"fields","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated fields to return.","example":"domain,target"},{"name":"shared-host","required":false,"in":"query","description":"Shared host","schema":{}},{"name":"domainName","required":false,"in":"path","description":"Domain name","schema":{}},{"name":"configSiteName","required":true,"in":"path","description":"Site configuration name","schema":{}}],"responses":{"200":{"description":"Host mappings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/base-url":{"get":{"operationId":"SiteController_getSiteBaseUrl","summary":"Get the org's public site URL","description":"The origin every customer-facing link should be built on, from the one resolver all public links use: the site's custom domain, then its platform host, then the org's own host when it has no site. `siteName` narrows it to one site; without it the org's default site answers. Build links on this rather than assembling a host on the client.\n\n#### Signature\n\n```http\nGET /site/base-url (siteName?: string) -> { url }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"siteName","required":false,"in":"query","schema":{"type":"string"},"example":"acme-shop"},{"name":"feature","required":false,"in":"query","description":"Resolve the default site’s configured form page instead of its origin.","schema":{"enum":["forms"],"type":"string"}}],"responses":{"200":{"description":"{ url }","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","nullable":true}}},"example":{"url":"https://shop.example.com"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/org-sites/{siteOrgId}":{"get":{"operationId":"SiteController_getOrgSites","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"siteOrgId","required":true,"in":"path","schema":{"type":"string"},"description":"Org whose sites to list.","example":"org_4821"}],"responses":{"200":{"description":"Sites","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"Could not read organization '<orgId>' — The org record could not be read — an infrastructure problem, not a client one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Could not read organization '<orgId>'","path":"/site/org-sites/{siteOrgId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Site"],"summary":"List an organization's sites","description":"Every site belonging to an org. `siteOrgId` is a path parameter separate from the `orgid` header, which is what lets an operator list another org's sites.\n\n#### Signature\n\n```http\nGET /site/org-sites/{siteOrgId} (siteOrgId: string) -> Sites\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | ORG_UNREADABLE | Could not read organization '<orgId>' | The org record could not be read — an infrastructure problem, not a client one. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/create-site`"}},"/site/attach-domain":{"post":{"operationId":"SiteController_registerSiteDomainName","summary":"Attach a domain to a site","description":"Routes a hostname to a site. DNS still has to point at the platform for the domain to resolve — attaching here creates the mapping, it does not configure the registrar.\n\n#### Signature\n\n```http\nPOST /site/attach-domain (body) -> The mapping result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /site/unregister-site-domain/{siteId}/{domainName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The domain and target site.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","domain":"shop.example.com"}}}},"responses":{"201":{"description":"The mapping result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/site/attach-domain","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/{site}/features/{feature}/link":{"post":{"operationId":"SiteController_linkFeature","summary":"Link a site feature from the footer or menu","description":"Adds a link to a feature's page (for example Gift cards → `/gift-card`) to **every page** of the site — into its `<footer>`, or with `where: \"nav\"` into its `<nav>` (else `<header>`) — styled like the links already there. `remove: true` takes it out again. The feature must be switched on in Site Features and have a page; `unsubscribe` and `gift-card` have built-in pages. Pages that already link to it, or have no footer/menu, are left alone and reported.\n\n#### Signature\n\n```http\nPOST /site/{site}/features/{feature}/link (site: string, feature: string, body) -> { href, where, label, updated, alreadyLinked, noPlace, removed } — page names in each list\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Edits the HTML of every page of the site.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SITE_NOT_FOUND | Site acme-shop not found | No site with that name in the org. | — |\n| `400` | FEATURE_OFF | Turn gift-card on in Site Features first | Linking a feature that is not enabled on the site. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"site","required":true,"in":"path","schema":{"type":"string"},"description":"Site name.","example":"acme-shop"},{"name":"feature","required":true,"in":"path","schema":{"type":"string"},"description":"Site feature key.","example":"gift-card"}],"responses":{"201":{"description":"{ href, where, label, updated, alreadyLinked, noPlace, removed } — page names in each list","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"href":"/gift-card","where":"footer","label":"Gift Cards","updated":["home","about"],"alreadyLinked":["contact"],"noPlace":[],"removed":[]}}}},"400":{"description":"Turn gift-card on in Site Features first — Linking a feature that is not enabled on the site.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Turn gift-card on in Site Features first","path":"/site/{site}/features/{feature}/link","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Site acme-shop not found — No site with that name in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Site acme-shop not found","path":"/site/{site}/features/{feature}/link","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"],"requestBody":{"description":"Where, and what to call it.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"where":{"type":"string","enum":["footer","nav"],"default":"footer"},"label":{"type":"string","description":"Defaults to the feature name in title case."},"remove":{"type":"boolean"}}},"example":{"where":"footer","label":"Gift cards"}}}}}},"/site/make-site-template":{"post":{"operationId":"SiteController_makeSiteTemplate","summary":"Register a site as a template","description":"Records an existing site in the shared org as a reusable template, so `POST /site/create-site` can name it and start from a copy of its pages.\n\nThe template is a pointer, not a snapshot: it stores which org owns the site and what it is called, and applying it reads that site live. Editing the source site updates the template.\n\n#### Signature\n\n```http\nPOST /site/make-site-template (body) -> The registered template and its stored content\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Only the shared org may register a template — the registry is a curated gallery.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | NOT_SHARED_ORG | Not allowed, invalid org | The caller is not the shared org. | Register templates from the shared org. |\n| `404` | SITE_NOT_FOUND | Site not found | No site by that name exists in the owning org. | Check `site` and `orgId`. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /site/create-site`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The site to register, and the org that owns it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["site"],"properties":{"site":{"type":"string","description":"Site name. Lowercased.","example":"acme-shop"},"orgId":{"type":"string","description":"Org that owns the site. Defaults to the caller.","example":"acme-retail"}}},"example":{"site":"storefront-basic","orgId":"appmint"}}}},"responses":{"201":{"description":"The registered template and its stored content","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Not allowed, invalid org — The caller is not the shared org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not allowed, invalid org","path":"/site/make-site-template","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Site not found — No site by that name exists in the owning org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Site not found","path":"/site/make-site-template","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/create-site":{"post":{"operationId":"SiteController_createSite","summary":"Create a site","description":"Provisions a new site: the site record, its default platform domain and its host mappings. Must start with a letter and contain only lowercase letters, numbers and hyphens.\n\nThe name becomes part of the default domain and is not renameable afterwards — changing it later means creating a new site and moving the domains.\n\n#### Signature\n\n```http\nPOST /site/create-site (body) -> The created site\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- The site name is fixed once created.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_SITE_NAME | Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens | The site name breaks the naming rule. | Must start with a letter and contain only lowercase letters, numbers and hyphens. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/clone-site`\n- `POST /site/add`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The site to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","title":"Acme Shop","templateName":"storefront"}}}},"responses":{"201":{"description":"The created site","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens — The site name breaks the naming rule.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens","path":"/site/create-site","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/clone-site":{"post":{"operationId":"SiteController_cloneSite","summary":"Clone a site","description":"Copies a site — content and configuration — from a source org and site to a destination. All four of `sourceOrgId`, `sourceSiteName`, `destOrgId` and `destSiteName` are required; the destination is created, not merged into.\n\n#### Signature\n\n```http\nPOST /site/clone-site (body) -> The cloned site\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Copies across orgs — check the destination org is the intended one.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | CLONE_FIELDS_REQUIRED | sourceOrgId, sourceSiteName, destOrgId, destSiteName are required | Any of the four is missing. | Supply all four. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/create-site`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Source and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sourceOrgId","sourceSiteName","destOrgId","destSiteName"],"properties":{"sourceOrgId":{"type":"string","example":"org_4821"},"sourceSiteName":{"type":"string","example":"acme-shop"},"destOrgId":{"type":"string","example":"org_7712"},"destSiteName":{"type":"string","example":"acme-shop-copy"}}},"example":{"sourceOrgId":"org_4821","sourceSiteName":"acme-shop","destOrgId":"org_7712","destSiteName":"acme-shop-copy"}}}},"responses":{"201":{"description":"The cloned site","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"sourceOrgId, sourceSiteName, destOrgId, destSiteName are required — Any of the four is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"sourceOrgId, sourceSiteName, destOrgId, destSiteName are required","path":"/site/clone-site","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/delete-site/{siteOrgId}/{siteId}":{"delete":{"operationId":"SiteController_deleteSiteHostMapping","summary":"Delete a site","description":"Deletes a site and its host mappings.\n\n**Known defect:** the handler declares both path parameters with the same name (`siteOrgId`), so `siteId` is never bound — the second segment is ignored and the value used for both is the first. Pass the id you intend in the **first** segment until this is fixed.\n\n#### Signature\n\n```http\nDELETE /site/delete-site/{siteOrgId}/{siteId} (siteOrgId: string, siteId: string) -> The delete result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Both path parameters are declared as `siteOrgId`; the second segment is not bound.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/remove`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"siteOrgId","required":true,"in":"path","schema":{"type":"string"},"description":"Org id — and, because of the defect below, the value used for the site id too.","example":"org_4821"},{"name":"siteId","required":true,"in":"path","description":"Site id. Currently ignored by the handler.","schema":{"type":"string"},"example":"site_77"}],"responses":{"200":{"description":"The delete result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/site/delete-site/{siteOrgId}/{siteId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/change-site-domain-names":{"post":{"operationId":"SiteController_changeSiteDomainNames","summary":"Rename site domains in bulk","description":"Renames domain mappings in bulk — the body is an **array**, each entry naming the site plus the old and new hostname. Used when a domain moves.\n\nEach rename takes the old hostname out of service the moment it is applied, so send them when DNS for the new names is already in place.\n\n#### Signature\n\n```http\nPOST /site/change-site-domain-names (body) -> Per-rename results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The body is an array, not an object.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/fix-domains`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The renames.","required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["siteName","oldName","newName"],"properties":{"siteName":{"type":"string","example":"acme-shop"},"oldName":{"type":"string","example":"old.example.com"},"newName":{"type":"string","example":"shop.example.com"}}}},"example":[{"siteName":"acme-shop","oldName":"old.example.com","newName":"shop.example.com"}]}}},"responses":{"201":{"description":"Per-rename results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/unregister-site-domain/{siteId}/{domainName}":{"delete":{"operationId":"SiteController_unregisterSiteDomainName","summary":"Unregister a site domain","description":"Removes a hostname mapping. Traffic to that hostname stops reaching the site immediately — visitors get whatever the platform serves for an unmapped host.\n\n#### Signature\n\n```http\nDELETE /site/unregister-site-domain/{siteId}/{domainName} (siteId: string, domainName: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `ContentAdmin`.\n\n#### Notes\n\n- Takes the hostname offline at once.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_ID_OR_DOMAIN_REQUIRED | Site ID or domain name is required | Either segment is empty. | Supply both. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/attach-domain`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"siteId","required":true,"in":"path","description":"Site id.","schema":{"type":"string"},"example":"site_77"},{"name":"domainName","required":true,"in":"path","description":"Hostname to remove.","schema":{"type":"string"},"example":"shop.example.com"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site ID or domain name is required — Either segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site ID or domain name is required","path":"/site/unregister-site-domain/{siteId}/{domainName}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/page/{hostName}/{siteName}/*":{"get":{"operationId":"SiteController_getPage","summary":"Render a site page by path","description":"Renders a page at an arbitrary depth — the trailing wildcard captures the whole remaining path, so `/a/b/c` resolves as one page path rather than three parameters.\n\n#### Signature\n\n```http\nGET /site/page/{hostName}/{siteName}/* (hostName: string, siteName: string, path: string) -> The rendered page payload\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/page/{hostName}/{siteName}/*`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"hostName","required":true,"in":"path","description":"Hostname being served.","schema":{"type":"string"},"example":"shop.example.com"},{"name":"siteName","required":true,"in":"path","description":"Site name.","schema":{"type":"string"},"example":"acme-shop"},{"name":"0","required":false,"in":"path","description":"Additional path parameters","schema":{"type":"string"}},{"name":"path","in":"path","required":true,"description":"Wildcard — the full remaining page path.","schema":{"type":"string"},"example":"products/cola-330"}],"responses":{"200":{"description":"The rendered page payload","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]},"post":{"operationId":"SiteController_getPagePost","summary":"Render a site page with a body","description":"The POST form of page rendering, for pages driven by submitted data — a form post or a search. Same resolution as the GET form, with the body available to the page.\n\n#### Signature\n\n```http\nPOST /site/page/{hostName}/{siteName}/* (hostName: string, siteName: string, path: string, body) -> The rendered page payload\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/page/{hostName}/{siteName}/*`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"hostName","required":true,"in":"path","description":"Hostname being served.","schema":{"type":"string"},"example":"shop.example.com"},{"name":"siteName","required":true,"in":"path","description":"Site name.","schema":{"type":"string"},"example":"acme-shop"},{"name":"0","required":false,"in":"path","description":"Additional path parameters","schema":{"type":"string"}},{"name":"path","in":"path","required":true,"description":"Wildcard — the full remaining page path.","schema":{"type":"string"},"example":"search"}],"requestBody":{"description":"Data for the page.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"q":"cola"}}}},"responses":{"201":{"description":"The rendered page payload","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/page/{hostName}/{siteName}":{"get":{"operationId":"SiteController_getPageIndex","summary":"Render a site page","description":"Renders the root page of a site for a given host. Query parameters are passed through to the page, so they reach page-level data resolution.\n\n#### Signature\n\n```http\nGET /site/page/{hostName}/{siteName} (hostName: string, siteName: string) -> The rendered page payload\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A `page` record can hold its content in four shapes, and the renderer prefers them in this order: `data.screens[]`, then `data.html`, then `data.content`, then `data.sections[]`.\n- **`data.screens` overrides `data.html`.** Any page ever saved in the visual editor carries `screens`, so later writes to `data.html` succeed with 201 and change nothing on screen — with no warning anywhere. When a deploy script takes ownership of a page, clear the other shapes in the same write: `{\"data.html\": \"…\", \"data.screens\": [], \"data.sections\": [], \"data.content\": \"\"}`.\n- Page CSS and JavaScript live in a **top-level `style` object on the page record — a sibling of `data`**, with slots `javascript`, `css`, `scriptLinks` and `styleLinks`. `data.style` is read by nothing: a payload written there is stored, returns 201 and never runs.\n- `style.javascript` is injected into the document HEAD, so it executes before the body is parsed and before the browser runtime mounts. Code there needs a readiness gate rather than `DOMContentLoaded`, which may have fired before the script was injected.\n- A `<script>` with a body inside `data.html` never executes — the HTML is parsed to a virtual DOM with `blockTextElements: { script: false }`, which discards script bodies. The element survives as an empty node. Inline event-handler attributes DO run, because attributes survive parsing.\n- `<link rel=\"icon\">` tags in page HTML are deliberately stripped so a template cannot shadow the real favicon, which is owned by the site record.\n- An unknown path renders the site fallback with a **200**, not a 404 — so a status code is not a validity check. Verify a deployed route by its `<h1>`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/page/{hostName}/{siteName}/*`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"hostName","required":true,"in":"path","description":"Hostname being served.","schema":{"type":"string"},"example":"shop.example.com"},{"name":"siteName","required":true,"in":"path","description":"Site name.","schema":{"type":"string"},"example":"acme-shop"},{"name":"0","required":false,"in":"path","description":"Additional path parameters","schema":{}}],"responses":{"200":{"description":"The rendered page payload","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/page-data/{datatype}/{dataId}":{"get":{"operationId":"SiteController_getPageData","summary":"Get page data","description":"Fetches the record backing a page — a product for a product page, a post for an article page. `dataId` is optional: omit it for the collection, supply it for one record.\n\n#### Signature\n\n```http\nGET /site/page-data/{datatype}/{dataId} (datatype: string, dataId: string) -> The page data\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/page/{hostName}/{siteName}/*`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"datatype","required":true,"in":"path","description":"DataType name.","schema":{"type":"string"},"example":"product"},{"name":"dataId","required":true,"in":"path","description":"Record id. Omit for the collection.","schema":{"type":"string"},"example":"PRD-4821"}],"responses":{"200":{"description":"The page data","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/site-map":{"post":{"operationId":"SiteController_createSiteMap","summary":"Create a site map","description":"Generates the sitemap for a site — what search engines crawl.\n\n#### Signature\n\n```http\nPOST /site/site-map (body) -> The site map\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/get-site-host-mappings/{siteName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which site to map.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop"}}}},"responses":{"201":{"description":"The site map","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/site/site-map","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/app/dsl/{name}":{"get":{"operationId":"SiteController_getAppDSL","summary":"Get an app DSL","description":"Returns the DSL definition for a named app — the declarative description a client renders from.\n\n**Public route:** it carries no authentication, so any DSL served here is readable by anyone who knows the name. Keep credentials and private endpoints out of DSL definitions.\n\n#### Signature\n\n```http\nGET /site/app/dsl/{name} (name: string) -> The DSL definition\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — treat the content as public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /site/page-data/{datatype}/{dataId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"App name.","schema":{"type":"string"},"example":"booking-widget"}],"responses":{"200":{"description":"The DSL definition","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/add":{"post":{"operationId":"SiteController_addSite","summary":"Add a site","description":"Registers a site and its host mappings. Overlaps with `create-site`; which one applies depends on whether the underlying site content already exists.\n\n#### Signature\n\n```http\nPOST /site/add (body) -> The added site\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `ContentAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_SITE_NAME | Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens | The site name breaks the naming rule. | Must start with a letter and contain only lowercase letters, numbers and hyphens. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/remove`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The site to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","domain":"shop.example.com"}}}},"responses":{"200":{"description":"Site successfully created","content":{"application/json":{"schema":{"type":"object","properties":{"site":{"type":"object","description":"Created site object"},"domainRecord":{"type":"array","description":"Domain registration records if domain was provided"},"message":{"type":"string","description":"Success message"}}}}}},"201":{"description":"The added site","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens — The site name breaks the naming rule.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens","path":"/site/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/remove":{"post":{"operationId":"SiteController_removeSite","summary":"Remove a site","description":"Removes a site and its host mappings. The site stops resolving on every domain routed to it — confirm which domains are affected with `GET /site/get-site-host-mappings/{siteName}` first.\n\n#### Signature\n\n```http\nPOST /site/remove (body) -> The removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Takes the site offline on all of its domains.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /site/delete-site/{siteOrgId}/{siteId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which site to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop"}}}},"responses":{"200":{"description":"Site successfully removed","content":{"application/json":{"schema":{"type":"object","properties":{"site":{"type":"object","description":"Deleted site information"},"domains":{"type":"array","description":"Domain unregistration results"},"resources":{"type":"object","description":"Resource deletion results"},"message":{"type":"string","description":"Success message"}}}}}},"201":{"description":"The removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/site/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/create":{"post":{"operationId":"SiteController_createSiteWithPlanValidation","summary":"Create a site with plan validation (legacy)","description":"Legacy create path that checks the org's plan limits before provisioning. Kept for existing callers — new integrations should use `POST /site/create-site`.\n\n#### Signature\n\n```http\nPOST /site/create (body) -> The created site\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Legacy — prefer `POST /site/create-site`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_SITE_NAME | Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens | The site name breaks the naming rule. | Must start with a letter and contain only lowercase letters, numbers and hyphens. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/create-site`\n- `GET /site/plan-limits`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The site to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","title":"Acme Shop"}}}},"responses":{"201":{"description":"The created site","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens — The site name breaks the naming rule.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid site name. Must start with a letter and contain only lowercase letters, numbers, and hyphens","path":"/site/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/usage-stats":{"get":{"operationId":"SiteController_getUsageStats","summary":"Get organization usage statistics","description":"Resource usage for the org's sites — what counts against the plan limits.\n\n#### Signature\n\n```http\nGET /site/usage-stats () -> Usage statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/plan-limits`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Usage statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/plan-limits":{"get":{"operationId":"SiteController_getPlanLimits","summary":"Get organization plan limits","description":"The org's plan ceilings — site count and resource allowances. Read alongside usage stats to know how much headroom is left before a create is refused.\n\n#### Signature\n\n```http\nGET /site/plan-limits () -> Plan limits\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/usage-stats`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Plan limits","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/update-domain/{domain}":{"post":{"operationId":"SiteController_updateDomain","summary":"Update a site domain","description":"Updates a site's domain configuration from the body. The `domain` path segment is optional and is **not read by the handler** — the domain is taken from the body.\n\n#### Signature\n\n```http\nPOST /site/update-domain/{domain} (domain: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `ContentAdmin`.\n\n#### Notes\n\n- The path parameter is ignored; put the domain in the body.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/remove-domain/{domain}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Optional, and ignored — the handler reads the body.","example":"shop.example.com"},{"name":"siteName","required":true,"in":"path","description":"Site name","schema":{}}],"requestBody":{"description":"The domain configuration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","domain":"shop.example.com"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/site/update-domain/{domain}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/remove-domain/{domain}":{"post":{"operationId":"SiteController_removeDomain","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Optional, and ignored — the handler reads the body.","example":"shop.example.com"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"No domain to remove — The body names no domain, or the site has no such mapping.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"No domain to remove","path":"/site/remove-domain/{domain}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"],"summary":"Remove a site domain","description":"Removes a domain from a site. As with `update-domain`, the `domain` path segment is optional and ignored — the domain comes from the body.\n\n#### Signature\n\n```http\nPOST /site/remove-domain/{domain} (domain: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The path parameter is ignored; put the domain in the body.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | NO_DOMAIN_TO_REMOVE | No domain to remove | The body names no domain, or the site has no such mapping. | Check the mappings first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/update-domain/{domain}`","requestBody":{"description":"Which domain to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","domain":"shop.example.com"}}}}}},"/site/apply-template":{"post":{"operationId":"SiteController_applySiteTemplate","summary":"Apply a template to a site","description":"Applies a site template to an existing site. Template content overwrites the corresponding pages — this is not a merge, so a customised page matching a template page is replaced. `siteOrgId` defaults to the calling org.\n\n#### Signature\n\n```http\nPOST /site/apply-template (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `ContentAdmin`.\n\n#### Notes\n\n- Overwrites existing pages that the template also defines.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/create-site`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Site and template.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteName","templateName"],"properties":{"siteOrgId":{"type":"string","description":"Defaults to the `orgid` header.","example":"org_4821"},"siteName":{"type":"string","example":"acme-shop"},"templateName":{"type":"string","example":"storefront"}}},"example":{"siteName":"acme-shop","templateName":"storefront"}}}},"responses":{"200":{"description":"Template successfully applied to site","content":{"application/json":{"schema":{"type":"object","properties":{"site":{"type":"object","description":"Updated site object"},"template":{"type":"string","description":"Applied template name"},"message":{"type":"string","description":"Success message"}}}}}},"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/site/apply-template","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/site/fix-domains":{"post":{"operationId":"SiteController_fixSiteDomains","summary":"Rebuild a site's domain mappings","description":"Repair tool: **removes every existing host mapping** for a site and recreates the default and custom domain mappings from the site record, then syncs the result to Cloudflare when it is configured.\n\nThe removal happens first, so the site is briefly unreachable on all of its domains while this runs, and a mapping that exists only in the routing table and not on the site record does not come back. Capture `GET /site/get-site-host-mappings/{siteName}` before running it.\n\nThe response reports partial failure in its `errors` and `cloudflareResults` arrays while still returning 200 — check `success`, not just the status code.\n\n#### Signature\n\n```http\nPOST /site/fix-domains (body) -> What was removed, added and synced\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Destructive-then-restorative: all mappings are dropped before being rebuilt.\n- Returns 200 even on partial failure — read `success` and `errors`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /site/get-site-host-mappings/{siteName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The site to repair.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteName"],"properties":{"siteOrgId":{"type":"string","description":"Defaults to the `orgid` header.","example":"org_4821"},"siteName":{"type":"string","pattern":"^[a-z][a-z0-9-]*$","example":"acme-shop"}}},"example":{"siteName":"acme-shop"}}}},"responses":{"200":{"description":"What was removed, added and synced","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"site":{"type":"string"},"removedDomains":{"type":"array","items":{"type":"string"}},"addedDomains":{"type":"array","items":{"type":"string"}},"cloudflareResults":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"target":{"type":"string"},"success":{"type":"boolean"},"recordId":{"type":"string"},"error":{"type":"string"}}}},"errors":{"type":"array","items":{"type":"string"}}}},"example":{"success":true,"message":"Domains rebuilt","site":"acme-shop","removedDomains":["shop.example.com"],"addedDomains":["acme-shop.platform.io","shop.example.com"],"cloudflareResults":[{"domain":"shop.example.com","target":"edge.platform.io","success":true,"recordId":"cf_881"}],"errors":[]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/site/fix-domains","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site"]}},"/dev-env/create":{"post":{"operationId":"DevEnvironmentController_createDevEnvironment","summary":"Create a development environment","description":"Provisions a dev environment — a container running a copy of a site where changes can be made without touching production. Hosting and domains are attached separately after creation.\n\n#### Signature\n\n```http\nPOST /dev-env/create (body) -> The created environment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DEV_ENV_DATA_REQUIRED | Dev environment data is required | The body is empty. | Supply the environment definition. |\n| `500` | SPINFORGE_UNCONFIGURED | SPINFORGE_PARTNER_KEY is not configured | The hosting partner key is missing from the server environment. | A deployment configuration problem — not fixable by the caller. |\n| `503` | ORG_UNREADABLE | Could not read organization '<orgId>' | The org record could not be read — an infrastructure problem, not a client one. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /dev-env/enable-hosting/{envName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The environment to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"envName":"acme-dev","siteName":"acme-shop"}}}},"responses":{"201":{"description":"The created environment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Dev environment data is required — The body is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Dev environment data is required","path":"/dev-env/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"SPINFORGE_PARTNER_KEY is not configured — The hosting partner key is missing from the server environment.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"SPINFORGE_PARTNER_KEY is not configured","path":"/dev-env/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"503":{"description":"Could not read organization '<orgId>' — The org record could not be read — an infrastructure problem, not a client one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Could not read organization '<orgId>'","path":"/dev-env/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Site · Dev environments"]}},"/dev-env/convert/{siteName}":{"post":{"operationId":"DevEnvironmentController_convertSiteToDevEnv","summary":"Convert a site to a development environment","description":"Turns an existing site into a dev environment. The site is converted in place rather than copied — the production site becomes the dev environment, so clone it first if it needs to keep serving.\n\n#### Signature\n\n```http\nPOST /dev-env/convert/{siteName} (siteName: string, body) -> The converted environment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Converts in place — clone the site first if production must keep running.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SITE_NOT_FOUND | Site not found <siteName> | No site with that name exists in the org. Returned as 422, not 404. | List the org's sites with `GET /site/org-sites/{siteOrgId}`. |\n| `400` | SITE_NAME_REQUIRED | site name is required | The path segment is empty. | Supply the site name. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /site/clone-site`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"siteName","required":true,"in":"path","description":"Site to convert.","schema":{"type":"string"},"example":"acme-shop"}],"responses":{"201":{"description":"The converted environment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"site name is required — The path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"site name is required","path":"/dev-env/convert/{siteName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Site not found <siteName> — No site with that name exists in the org. Returned as 422, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Site not found <siteName>","path":"/dev-env/convert/{siteName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"],"requestBody":{"description":"Conversion options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"envName":"acme-dev"}}}}}},"/dev-env/{envName}":{"delete":{"operationId":"DevEnvironmentController_deleteDevEnvironment","summary":"Delete a development environment","description":"Destroys a dev environment and its container. Anything that lives only inside the container — uncommitted work, local data — goes with it.\n\n#### Signature\n\n```http\nDELETE /dev-env/{envName} (envName: string) -> The delete result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Container-local state is not recoverable afterwards.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /dev-env/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"200":{"description":"The delete result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/{envName}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/enable-hosting/{envName}":{"post":{"operationId":"DevEnvironmentController_enableDevEnvHosting","summary":"Enable hosting for an environment","description":"Puts a dev environment on the network so it can be reached over HTTP. Until this runs the container exists but serves nothing.\n\n#### Signature\n\n```http\nPOST /dev-env/enable-hosting/{envName} (envName: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ContentAdmin`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n| `500` | SPINFORGE_UNCONFIGURED | SPINFORGE_PARTNER_KEY is not configured | The hosting partner key is missing from the server environment. | A deployment configuration problem — not fixable by the caller. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /dev-env/attach-domain/{envName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/enable-hosting/{envName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"SPINFORGE_PARTNER_KEY is not configured — The hosting partner key is missing from the server environment.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"SPINFORGE_PARTNER_KEY is not configured","path":"/dev-env/enable-hosting/{envName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Site · Dev environments"]}},"/dev-env/attach-domain/{envName}":{"post":{"operationId":"DevEnvironmentController_attachCustomDomainToDevEnv","summary":"Attach a custom domain to an environment","description":"Routes a custom hostname to a dev environment. DNS must point at the platform separately — this creates the mapping only.\n\n#### Signature\n\n```http\nPOST /dev-env/attach-domain/{envName} (envName: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n| `400` | CUSTOM_DOMAIN_REQUIRED | Custom domain is required | `domain` is missing. | Supply the hostname. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /dev-env/detach-domain/{envName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"requestBody":{"description":"The domain.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"dev.example.com"}}},"example":{"domain":"dev.example.com"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Custom domain is required — `domain` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Custom domain is required","path":"/dev-env/attach-domain/{envName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/attach-domain/{envName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/detach-domain/{envName}":{"post":{"operationId":"DevEnvironmentController_detachCustomDomainFromDevEnv","summary":"Detach a custom domain from an environment","description":"Removes a custom hostname from a dev environment; the environment stays reachable on its container domain.\n\n#### Signature\n\n```http\nPOST /dev-env/detach-domain/{envName} (envName: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n| `400` | CUSTOM_DOMAIN_REQUIRED | Custom domain is required | `domain` is missing. | Supply the hostname. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /dev-env/attach-domain/{envName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"requestBody":{"description":"The domain to detach.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"dev.example.com"}}},"example":{"domain":"dev.example.com"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Custom domain is required — `domain` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Custom domain is required","path":"/dev-env/detach-domain/{envName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/detach-domain/{envName}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/get/{envName}":{"get":{"operationId":"DevEnvironmentController_getDevEnv","summary":"Get a development environment","description":"Fetches one environment with its configuration and hosting state.\n\n#### Signature\n\n```http\nGET /dev-env/get/{envName} (envName: string) -> The environment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n| `503` | DEV_ENVS_UNREADABLE | Could not read dev environments for organization '<orgId>' | The org's environments could not be read. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /dev-env/{envName}/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"requestBody":{"required":true,"description":"Custom domain details","content":{"application/json":{"schema":{"type":"object","properties":{"customDomain":{"type":"string","description":"Custom domain name"}},"required":["customDomain"]}}}},"responses":{"200":{"description":"The environment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/get/{envName}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"Could not read dev environments for organization '<orgId>' — The org's environments could not be read.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Could not read dev environments for organization '<orgId>'","path":"/dev-env/get/{envName}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Site · Dev environments"]}},"/dev-env/get-spinforge/{devEnvName}":{"get":{"operationId":"DevEnvironmentController_getDevEnvsAndUser","summary":"Get SpinForge details for an environment","description":"Returns the environment along with the hosting-partner (SpinForge) session detail a client needs to open it. Requires the partner key to be configured server-side.\n\n#### Signature\n\n```http\nGET /dev-env/get-spinforge/{devEnvName} (devEnvName: string) -> The environment and hosting session detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DEV_ENV_NAME_REQUIRED | devEnvName path parameter is required | The path segment is empty. | Supply the environment name. |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n| `500` | SPINFORGE_UNCONFIGURED | SPINFORGE_PARTNER_KEY is not configured | The hosting partner key is missing from the server environment. | A deployment configuration problem — not fixable by the caller. |\n| `502` | SPINFORGE_NO_TOKEN | SpinForge partner auth returned no token | The hosting partner authenticated but issued no token. | An upstream failure — retry, then escalate. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /dev-env/send-session-invitation`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"devEnvName","required":true,"in":"path","schema":{"type":"string"},"description":"Dev environment name.","example":"acme-dev"}],"responses":{"200":{"description":"The environment and hosting session detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"devEnvName path parameter is required — The path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"devEnvName path parameter is required","path":"/dev-env/get-spinforge/{devEnvName}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/get-spinforge/{devEnvName}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"SPINFORGE_PARTNER_KEY is not configured — The hosting partner key is missing from the server environment.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"SPINFORGE_PARTNER_KEY is not configured","path":"/dev-env/get-spinforge/{devEnvName}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"502":{"description":"SpinForge partner auth returned no token — The hosting partner authenticated but issued no token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"SpinForge partner auth returned no token","path":"/dev-env/get-spinforge/{devEnvName}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Site · Dev environments"]}},"/dev-env/{envName}/status":{"get":{"operationId":"DevEnvironmentController_getContainerStatus","summary":"Get container status","description":"Whether the environment's container is running, and its current state.\n\n#### Signature\n\n```http\nGET /dev-env/{envName}/status (envName: string) -> The container status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /dev-env/{envName}/logs`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"200":{"description":"The container status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/{envName}/status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/{envName}/logs":{"get":{"operationId":"DevEnvironmentController_getContainerLogs","summary":"Get container logs","description":"Recent log output from the environment's container — the first place to look when a dev environment misbehaves. Logs can carry whatever the running application printed, so treat the output as potentially sensitive.\n\n#### Signature\n\n```http\nGET /dev-env/{envName}/logs (envName: string) -> The container logs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Log content is not filtered.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /dev-env/{envName}/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"200":{"description":"The container logs","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/{envName}/logs","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/{envName}/start":{"post":{"operationId":"DevEnvironmentController_startContainer","summary":"Start a container","description":"Starts the environment's container.\n\n#### Signature\n\n```http\nPOST /dev-env/{envName}/start (envName: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /dev-env/{envName}/stop`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/{envName}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/{envName}/stop":{"post":{"operationId":"DevEnvironmentController_stopContainer","summary":"Stop a container","description":"Stops the environment's container. Anyone using the environment loses it immediately, and it stops serving on its domains.\n\n#### Signature\n\n```http\nPOST /dev-env/{envName}/stop (envName: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Takes the environment offline.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /dev-env/{envName}/start`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/{envName}/stop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/{envName}/restart":{"post":{"operationId":"DevEnvironmentController_restartContainer","summary":"Restart a container","description":"Stops and starts the container. Brief downtime, and in-container process state is lost — the filesystem is kept.\n\n#### Signature\n\n```http\nPOST /dev-env/{envName}/restart (envName: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /dev-env/{envName}/rebuild`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/{envName}/restart","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/{envName}/rebuild":{"post":{"operationId":"DevEnvironmentController_rebuildContainer","summary":"Rebuild a container","description":"Rebuilds the container image and replaces the running container. Heavier than a restart: anything written inside the container that is not part of the image or a mounted volume does not survive.\n\n#### Signature\n\n```http\nPOST /dev-env/{envName}/rebuild (envName: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Discards container-local filesystem changes.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEV_ENV_NOT_FOUND | Dev environment <envName> not found | No dev environment has that name in this org. | Check the name with `GET /dev-env/get/{envName}`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /dev-env/{envName}/restart`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"envName","required":true,"in":"path","description":"Dev environment name.","schema":{"type":"string"},"example":"acme-dev"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Dev environment <envName> not found — No dev environment has that name in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Dev environment <envName> not found","path":"/dev-env/{envName}/rebuild","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"]}},"/dev-env/send-session-invitation":{"post":{"operationId":"DevEnvironmentController_sendSessionInvitation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The send result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Email is required — `email` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email is required","path":"/dev-env/send-session-invitation","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Site · Dev environments"],"summary":"Send a session invitation","description":"Emails someone an invitation to join a dev-environment session. This sends real mail to the address given — check it before calling.\n\n#### Signature\n\n```http\nPOST /dev-env/send-session-invitation (body) -> The send result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends an email — outward-facing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_REQUIRED | Email is required | `email` is missing. | Supply the recipient address. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /dev-env/get-spinforge/{devEnvName}`","requestBody":{"description":"Who to invite.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","example":"dev@example.com"},"envName":{"type":"string","example":"acme-dev"}}},"example":{"email":"dev@example.com","envName":"acme-dev"}}}}}},"/chat/engage":{"post":{"operationId":"ChatController_engage","summary":"Engage a website visitor","description":"Staff only. Pushes a notice or a chat invite into one visitor's widget, by the `deviceId` it reported (from Live View). Works whether or not the sender is connected to chat. Delivered over the widget's socket; nothing is stored.\n\n#### Signature\n\n```http\nPOST /chat/engage (body) -> { success: true, messageId }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | STAFF_ONLY | Sign in as staff to engage a visitor. | The caller is not a signed-in user. | — |\n| `400` | DEVICE_REQUIRED | Say which visitor (deviceId). Their widget has not reported a device. | No deviceId. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ success: true, messageId }","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"messageId":{"type":"string"}}}}}},"400":{"description":"Say which visitor (deviceId). Their widget has not reported a device. — No deviceId.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say which visitor (deviceId). Their widget has not reported a device.","path":"/chat/engage","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as staff to engage a visitor. — The caller is not a signed-in user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as staff to engage a visitor.","path":"/chat/engage","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["deviceId","message"],"properties":{"deviceId":{"type":"string"},"message":{"type":"string"},"kind":{"type":"string","enum":["notice","chat-invite"],"default":"chat-invite"},"title":{"type":"string"}}},"example":{"deviceId":"dev_7Kq2M9","message":"Need help choosing a size?","kind":"chat-invite"}}}}}},"/chat/ice-servers":{"get":{"operationId":"ChatController_getIceServers","summary":"Get ICE servers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"ICE server configuration","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Returns the STUN and TURN servers a WebRTC client needs to establish a voice or video connection.\n\nFetch these when opening a call rather than caching them — TURN credentials are short-lived, and a stale set is the usual cause of a call that connects on the same network but fails across NAT.\n\n#### Signature\n\n```http\nGET /chat/ice-servers () -> ICE server configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- TURN credentials expire. Do not cache the response between sessions.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /chat/live/{chatId}/{userId}`"}},"/chat/agents/online":{"get":{"operationId":"ChatController_getOnlineAgents","summary":"List online agents","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["available","busy","away"]},"example":"available"},{"name":"skill","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to agents with a skill.","example":"billing"}],"responses":{"200":{"description":"Online agents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"The agents currently available, optionally filtered by status or skill. The read behind a routing decision — who can take this chat right now.\n\n#### Signature\n\n```http\nGET /chat/agents/online (status?: string, skill?: string) -> Online agents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/agents/{email}/presence`"}},"/chat/agents/{email}/presence":{"get":{"operationId":"ChatController_getAgentPresence","summary":"Get an agent's presence","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Agent email address.","example":"agent@acme.com"}],"responses":{"200":{"description":"The agent's presence","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/chat/agents/{email}/presence","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"The current presence of one agent — whether they are online, their status, and how many chats they are handling.\n\n#### Signature\n\n```http\nGET /chat/agents/{email}/presence (email: string) -> The agent's presence\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Agent not found | No agent has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /chat/agents/{email}/status`"}},"/chat/customers/online":{"get":{"operationId":"ChatController_getOnlineCustomers","summary":"List online customers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Online customers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Customers currently present on the site — who could be reached with a proactive chat invitation.\n\n#### Signature\n\n```http\nGET /chat/customers/online () -> Online customers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/customers/{email}/journey`"}},"/chat/customers/{email}/journey":{"get":{"operationId":"ChatController_getCustomerJourney","summary":"Get a customer's interaction journey","description":"The customer's full interaction history across channels — what an agent should read before answering, so the customer is not asked to repeat themselves.\n\n`limit` caps how far back it goes.\n\n#### Signature\n\n```http\nGET /chat/customers/{email}/journey (email: string, limit?: integer) -> The customer journey\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/messages/{email}/{createdAfter}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Customer email.","example":"ada@example.com"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"How many interactions to return.","example":50}],"responses":{"200":{"description":"The customer journey","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/presence/stats":{"get":{"operationId":"ChatController_getPresenceStats","summary":"Get presence statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Presence statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Aggregate presence across the org — how many agents are online, by status. The staffing view against queue depth.\n\n#### Signature\n\n```http\nGET /chat/presence/stats () -> Presence statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/queue/stats`"}},"/chat/agents/{email}/status":{"post":{"operationId":"ChatController_setAgentStatus","summary":"Set an agent's status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Agent email address.","example":"agent@acme.com"}],"responses":{"201":{"description":"The updated presence","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/chat/agents/{email}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Sets an agent's availability. Only `available` agents receive routed chats — moving to `busy` or `away` takes them out of routing without disconnecting the chats they already hold.\n\n#### Signature\n\n```http\nPOST /chat/agents/{email}/status (email: string, body) -> The updated presence\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing chats are unaffected — this changes routing eligibility only.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Agent not found | No agent has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/agents/online`","requestBody":{"description":"The status to set.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["available","busy","away"],"description":"Only `available` receives new chats.","example":"busy"}}},"example":{"status":"busy"}}}}}},"/chat/queue":{"get":{"operationId":"ChatController_getQueue","summary":"Get the chat queue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":false,"in":"query","schema":{"type":"string"},"description":"Which queue. Omit for the default.","example":"support"}],"responses":{"200":{"description":"Queued chats","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"The chats currently waiting, in the order they will be served.\n\n#### Signature\n\n```http\nGET /chat/queue (name?: string) -> Queued chats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/queue/stats`"},"post":{"operationId":"ChatController_enqueueChat","summary":"Enqueue a chat","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The queued chat, with its position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Puts a chat into the routing queue to wait for an agent.\n\nUse `skill` to require a particular competency and `priority` to jump the line — a higher number is served sooner. `context` travels with the chat, so whatever the customer already told a bot arrives with them.\n\n#### Signature\n\n```http\nPOST /chat/queue (body) -> The queued chat, with its position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A `skill` no online agent has leaves the chat queued indefinitely — check `GET /chat/agents/online` before requiring one.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/queue/position/{chatId}`\n- `DELETE /chat/queue/{chatId}`","requestBody":{"description":"The chat to queue.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["chatId","customerEmail"],"properties":{"chatId":{"type":"string","example":"CHT-4821"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"queue":{"type":"string","description":"Which queue to join. Omit for the default.","example":"support"},"skill":{"type":"string","description":"Required agent skill.","example":"billing"},"priority":{"type":"number","description":"Higher is served sooner.","example":10},"context":{"type":"object","additionalProperties":true,"description":"Carried to the agent — prior answers, cart contents, the page they were on."}}},"examples":{"basic":{"summary":"Join the default queue","value":{"chatId":"CHT-4821","customerEmail":"ada@example.com","customerName":"Ada Lovelace"}},"skilled":{"summary":"Require a skill and raise priority","value":{"chatId":"CHT-4821","customerEmail":"ada@example.com","queue":"support","skill":"billing","priority":10,"context":{"orderNumber":"A7K2M9QX4"}}}}}}}}},"/chat/queue/stats":{"get":{"operationId":"ChatController_getQueueStats","summary":"Get queue statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":false,"in":"query","schema":{"type":"string"},"description":"Which queue. Omit for the default.","example":"support"}],"responses":{"200":{"description":"Queue statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Depth and wait times for a queue — read alongside `presence/stats` to see whether waits are a staffing problem or a routing one.\n\n#### Signature\n\n```http\nGET /chat/queue/stats (name?: string) -> Queue statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/presence/stats`"}},"/chat/queue/position/{chatId}":{"get":{"operationId":"ChatController_getQueuePosition","summary":"Get a chat's queue position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"chatId","required":true,"in":"path","schema":{"type":"string"},"description":"Chat session id.","example":"CHT-4821"},{"name":"name","required":false,"in":"query","schema":{"type":"string"},"description":"Which queue. Omit for the default.","example":"support"}],"responses":{"200":{"description":"The queue position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/queue/position/{chatId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Where a waiting chat sits in the queue — what a customer-facing \"you are 3rd in line\" indicator reads.\n\n#### Signature\n\n```http\nGET /chat/queue/position/{chatId} (chatId: string, name?: string) -> The queue position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Position moves as higher-priority chats are queued, so it can go up as well as down.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /chat/queue`"}},"/chat/queue/{chatId}":{"delete":{"operationId":"ChatController_dequeueChat","summary":"Remove a chat from the queue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"chatId","required":true,"in":"path","schema":{"type":"string"},"description":"Chat session id.","example":"CHT-4821"},{"name":"name","required":false,"in":"query","schema":{"type":"string"},"description":"Which queue. Omit for the default.","example":"support"}],"responses":{"200":{"description":"The dequeue result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/queue/{chatId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"description":"Dequeues a waiting chat — because the customer left, or because it was answered another way. The chat session itself is not ended by this.\n\n#### Signature\n\n```http\nDELETE /chat/queue/{chatId} (chatId: string, name?: string) -> The dequeue result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /chat/queue`"}},"/chat/config/{chatId}":{"get":{"operationId":"ChatController_getChatConfig","summary":"Get chat configuration","description":"The configuration for a chat session — its routing, branding and behaviour settings.\n\n#### Signature\n\n```http\nGET /chat/config/{chatId} (chatId: string) -> The chat configuration\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /chat/sessions`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"chatId","required":true,"in":"path","description":"Chat session id.","schema":{"type":"string"},"example":"CHT-4821"}],"responses":{"200":{"description":"The chat configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/config/{chatId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/get-profile/{userId}":{"get":{"operationId":"ChatController_getUserProfile","summary":"Get a chat user profile","description":"The profile of a chat participant — who the agent is actually talking to.\n\n#### Signature\n\n```http\nGET /chat/get-profile/{userId} (userId: string) -> The user profile\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | User not found | No user has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /chat/customers/{email}/journey`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"userId","required":true,"in":"path","description":"User identifier.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"The user profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"User not found — No user has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"User not found","path":"/chat/get-profile/{userId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/sessions":{"get":{"operationId":"ChatController_activeSessions","summary":"Get active chat sessions","description":"The chat sessions currently in progress across the org — the supervisor view of what is live right now.\n\n#### Signature\n\n```http\nGET /chat/sessions () -> Active sessions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/history/{chatId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Active sessions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/upload/{chatId}/{userId}":{"post":{"operationId":"ChatController_upload","summary":"Upload files to a chat","description":"Uploads one or more files into a chat session — screenshots, documents, whatever the customer needs to show. Multipart, and several files can be sent at once.\n\n#### Signature\n\n```http\nPOST /chat/upload/{chatId}/{userId} (chatId: string, userId: string, body) -> The uploaded files\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- This route does not read the `orgid` header — unlike the rest of the controller.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /chat/live/{chatId}/{userId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization ID","schema":{"type":"string"}},{"name":"chatId","required":true,"in":"path","description":"Chat session id.","schema":{"type":"string"},"example":"CHT-4821"},{"name":"userId","required":true,"in":"path","description":"Who is uploading.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"201":{"description":"The uploaded files","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"requestBody":{"description":"Multipart form with one or more files.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"string","format":"binary"},"description":"The files to upload."}}}}}}}},"/chat/live/{chatId}/{userId}":{"post":{"operationId":"ChatController_liveChat","summary":"Send a live chat message","description":"Sends a message into a live chat session.\n\nDelivery to connected clients happens over WebSocket; this is the HTTP path for a client that cannot hold a socket open, and for server-side sends.\n\n#### Signature\n\n```http\nPOST /chat/live/{chatId}/{userId} (chatId: string, userId: string, body) -> The sent message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /chat/save/{chatId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"chatId","required":true,"in":"path","description":"Chat session id.","schema":{"type":"string"},"example":"CHT-4821"},{"name":"userId","required":true,"in":"path","description":"Who is sending.","schema":{"type":"string"},"example":"agent@acme.com"}],"requestBody":{"description":"The message to send.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"text":{"type":"string","example":"Happy to help — can you confirm your order number?"}}},"example":{"text":"Happy to help — can you confirm your order number?"}}}},"responses":{"201":{"description":"The sent message","content":{"application/json":{"schema":{"type":"object","description":"A chat message.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"chatId":{"type":"string","example":"CHT-4821"},"userId":{"type":"string","example":"ada@example.com"},"text":{"type":"string","example":"Is anyone there?"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/live/{chatId}/{userId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/messages/{email}/{createdAfter}":{"get":{"operationId":"ChatController_getUserMessages","summary":"Get a user's messages","description":"Every chat message for an email address, across sessions. Supply `createdAfter` to fetch only what has arrived since a point in time — the polling path for a client without a live socket.\n\n#### Signature\n\n```http\nGET /chat/messages/{email}/{createdAfter} (email: string, createdAfter: string) -> The user's messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Omitting `createdAfter` returns the full history — always send it when polling.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/history/{chatId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"email","required":true,"in":"path","description":"User email.","schema":{"type":"string"},"example":"ada@example.com"},{"name":"createdAfter","required":true,"in":"path","schema":{"type":"string"},"description":"Return only messages after this timestamp.","example":"2026-08-30T09:00:00.000Z"}],"responses":{"200":{"description":"The user's messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A chat message.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"chatId":{"type":"string","example":"CHT-4821"},"userId":{"type":"string","example":"ada@example.com"},"text":{"type":"string","example":"Is anyone there?"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"createdAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/history/{chatId}":{"get":{"operationId":"ChatController_getChatHistory","summary":"Get chat history","description":"Every message in one chat session, in order.\n\n#### Signature\n\n```http\nGET /chat/history/{chatId} (chatId: string) -> The chat history\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /chat/transcript/send`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"chatId","required":true,"in":"path","description":"Chat session id.","schema":{"type":"string"},"example":"CHT-4821"}],"responses":{"200":{"description":"The chat history","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A chat message.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"chatId":{"type":"string","example":"CHT-4821"},"userId":{"type":"string","example":"ada@example.com"},"text":{"type":"string","example":"Is anyone there?"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"createdAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/history/{chatId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/save/{chatId}":{"post":{"operationId":"ChatController_saveMessage","summary":"Save a chat message","description":"Persists a message to a chat's history without sending it live — for recording messages that arrived by another route, or reconstructing a transcript.\n\n#### Signature\n\n```http\nPOST /chat/save/{chatId} (chatId: string, body) -> The saved message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Saves only — connected clients are not notified. Use `live` to actually deliver a message.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /chat/live/{chatId}/{userId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"chatId","required":true,"in":"path","description":"Chat session id.","schema":{"type":"string"},"example":"CHT-4821"}],"responses":{"201":{"description":"The saved message","content":{"application/json":{"schema":{"type":"object","description":"A chat message.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"chatId":{"type":"string","example":"CHT-4821"},"userId":{"type":"string","example":"ada@example.com"},"text":{"type":"string","example":"Is anyone there?"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/save/{chatId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"requestBody":{"description":"The message to store.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"ada@example.com","text":"Is anyone there?"}}}}}},"/chat/update/{chatId}":{"put":{"operationId":"ChatController_updateMessage","summary":"Update a chat message","description":"Edits a stored message. The transcript is what a dispute is settled on, so edit it only to correct a genuine error.\n\n#### Signature\n\n```http\nPUT /chat/update/{chatId} (chatId: string, body) -> The updated message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/history/{chatId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"chatId","required":true,"in":"path","description":"Chat session id.","schema":{"type":"string"},"example":"CHT-4821"}],"responses":{"200":{"description":"The updated message","content":{"application/json":{"schema":{"type":"object","description":"A chat message.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"chatId":{"type":"string","example":"CHT-4821"},"userId":{"type":"string","example":"ada@example.com"},"text":{"type":"string","example":"Is anyone there?"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/update/{chatId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"],"requestBody":{"description":"The message to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"messageId":"MSG-9912","text":"Corrected text"}}}}}},"/chat/transcript/send":{"post":{"operationId":"ChatController_sendTranscript","summary":"Send a chat transcript","description":"Emails the full transcript of a conversation to an address — what a customer gets after a chat ends, and what an agent forwards when escalating.\n\nThe transcript is the whole conversation. Check it contains nothing that should not leave the org before sending it to an external address.\n\n#### Signature\n\n```http\nPOST /chat/transcript/send (body) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends the entire conversation, including anything an agent said assuming it was internal.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Chat not found | No chat has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/history/{chatId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which chat, and where to send it.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"chatId":{"type":"string","example":"CHT-4821"},"email":{"type":"string","description":"Recipient.","example":"ada@example.com"}}},"example":{"chatId":"CHT-4821","email":"ada@example.com"}}}},"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found — No chat has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Chat not found","path":"/chat/transcript/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/chat/ai-active":{"get":{"operationId":"ChatController_getActiveAiChats","summary":"Check whether AI chat is active","description":"Whether the AI assistant is currently handling chats for this org — the flag a UI reads to decide whether to show \"you are talking to an assistant\".\n\n#### Signature\n\n```http\nGET /chat/ai-active () -> AI chat state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /chat/sessions`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"AI chat state","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Chat"]}},"/profile/system-orgs":{"get":{"operationId":"UsersController_getSystemOrgs","summary":"List system organizations","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Organizations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"The organizations visible to the caller — what a global login offers as an org picker.\n\n#### Signature\n\n```http\nGET /profile/system-orgs () -> Organizations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /global-login`"}},"/user/system-orgs":{"get":{"operationId":"UsersController_getSystemOrgs","summary":"List system organizations","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Organizations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"The organizations visible to the caller — what a global login offers as an org picker.\n\n#### Signature\n\n```http\nGET /user/system-orgs () -> Organizations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /global-login`"}},"/profile/whoami":{"get":{"operationId":"UsersController_whoAmI","summary":"Get the current user","description":"Returns the caller's own identity as the server sees it — the read a client makes on load to establish who is signed in.\n\n#### Signature\n\n```http\nGET /profile/whoami () -> The current user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /who-is`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The current user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"]}},"/user/whoami":{"get":{"operationId":"UsersController_whoAmI","summary":"Get the current user","description":"Returns the caller's own identity as the server sees it — the read a client makes on load to establish who is signed in.\n\n#### Signature\n\n```http\nGET /user/whoami () -> The current user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /who-is`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The current user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"]}},"/profile/who-is":{"get":{"operationId":"UsersController_whoIs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"authorization","required":true,"in":"header","schema":{"type":"string"}},{"name":"token","required":true,"in":"query","schema":{"type":"string"}},{"name":"value","in":"query","required":false,"description":"Identifier to resolve.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"The identified user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Identify a user","description":"Resolves who a given identifier belongs to.\n\n#### Signature\n\n```http\nGET /profile/who-is (value?: string) -> The identified user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /whoami`"}},"/user/who-is":{"get":{"operationId":"UsersController_whoIs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"authorization","required":true,"in":"header","schema":{"type":"string"}},{"name":"token","required":true,"in":"query","schema":{"type":"string"}},{"name":"value","in":"query","required":false,"description":"Identifier to resolve.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"The identified user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Identify a user","description":"Resolves who a given identifier belongs to.\n\n#### Signature\n\n```http\nGET /user/who-is (value?: string) -> The identified user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /whoami`"}},"/profile/signout":{"post":{"operationId":"UsersController_signOut","summary":"Sign out","description":"Ends the current session and invalidates its tokens.\n\n#### Signature\n\n```http\nPOST /profile/signout (body) -> Sign-out result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logout/{userId}`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Sign-out result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"Optional session detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/user/signout":{"post":{"operationId":"UsersController_signOut","summary":"Sign out","description":"Ends the current session and invalidates its tokens.\n\n#### Signature\n\n```http\nPOST /user/signout (body) -> Sign-out result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logout/{userId}`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Sign-out result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"Optional session detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/profile/guest/auth":{"post":{"operationId":"UsersController_guestAuth","summary":"Authenticate as a guest","description":"Issues a limited token for an unauthenticated visitor, so a storefront can act on their behalf before they have an account.\n\n#### Signature\n\n```http\nPOST /profile/guest/auth (body) -> A guest token\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Guest tokens are still credentials — scope them narrowly.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /app/register`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"appKey","in":"header","description":"Application Key","required":true,"schema":{"type":"string"}},{"name":"appId","in":"header","description":"Application requesting guest access.","required":true,"schema":{"type":"string"},"example":"storefront-web"}],"requestBody":{"description":"Guest context.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}},"responses":{"201":{"description":"A guest token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/guest/auth":{"post":{"operationId":"UsersController_guestAuth","summary":"Authenticate as a guest","description":"Issues a limited token for an unauthenticated visitor, so a storefront can act on their behalf before they have an account.\n\n#### Signature\n\n```http\nPOST /user/guest/auth (body) -> A guest token\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Guest tokens are still credentials — scope them narrowly.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /app/register`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"appKey","in":"header","description":"Application Key","required":true,"schema":{"type":"string"}},{"name":"appId","in":"header","description":"Application requesting guest access.","required":true,"schema":{"type":"string"},"example":"storefront-web"}],"requestBody":{"description":"Guest context.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}},"responses":{"201":{"description":"A guest token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/signin":{"post":{"operationId":"UsersController_signin","summary":"Sign in","description":"Authenticates a user and issues tokens.\n\n**Without an `orgid` header** the email finds the org through the account directory: when the password matches in exactly one org the session is issued for it (with `orgName`); when it matches in several the answer is `{ requiresOrgChoice: true, orgs }` and the client signs in again with the chosen `orgid`.\n\nThe answer may be a step rather than a session: a second-factor challenge (`requiresTwoFactor`, `challengeToken`, `twoFactorMethod` — the code is already sent unless the method is `authenticator`), a new-device check (`isNewDevice: true`, when the org turns on new-device verification), or tokens with `requiresPasswordChange: true` after a temporary password. Otherwise it is `{ user, orgId, rootOrg, sharedOrg, token, refreshToken }`.\n\nA wrong password and an unknown account produce the **same** error, deliberately — distinguishing them would let an attacker enumerate accounts. Do not add a more helpful message on the client.\n\nRepeated failures can lock the account, and a blocked device is refused before credentials are even checked.\n\n#### Signature\n\n```http\nPOST /profile/user/signin (body) -> A session, or the next step to take\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit this endpoint — the error deliberately gives an attacker nothing, but attempts are still cheap.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/refresh`\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":false,"in":"header","description":"The org to sign into. Omit it to let the email find the org (see the description).","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string"},"password":{"type":"string","format":"password"}}},"example":{"email":"ada@example.com","password":"correct horse battery staple"}}}},"responses":{"201":{"description":"A session, or the next step to take","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}},"examples":{"signedIn":{"summary":"Signed in","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","orgId":"acme","user":{"sk":"65f0c2a1e4b0a1b2c3d4e5f6","data":{"email":"ada@example.com"}}}},"secondFactor":{"summary":"Second factor required","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","message":"Verification code sent to your email"}},"newDevice":{"summary":"New device (when the org verifies new devices)","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","isNewDevice":true,"message":"New device detected. Verification code sent to your email."}},"temporaryPassword":{"summary":"Temporary password — change it now","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","requiresPasswordChange":true,"message":"Temporary password used. Please set a new password.","userId":"65f0c2a1e4b0a1b2c3d4e5f6","email":"ada@example.com"}},"chooseOrg":{"summary":"No orgid sent, several orgs match","value":{"requiresOrgChoice":true,"orgs":[{"orgId":"acme","displayName":"Acme"},{"orgId":"acme-eu","displayName":"Acme EU"}]}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/profile/user/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/profile/user/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/signin":{"post":{"operationId":"UsersController_signin","summary":"Sign in","description":"Authenticates a user and issues tokens.\n\n**Without an `orgid` header** the email finds the org through the account directory: when the password matches in exactly one org the session is issued for it (with `orgName`); when it matches in several the answer is `{ requiresOrgChoice: true, orgs }` and the client signs in again with the chosen `orgid`.\n\nThe answer may be a step rather than a session: a second-factor challenge (`requiresTwoFactor`, `challengeToken`, `twoFactorMethod` — the code is already sent unless the method is `authenticator`), a new-device check (`isNewDevice: true`, when the org turns on new-device verification), or tokens with `requiresPasswordChange: true` after a temporary password. Otherwise it is `{ user, orgId, rootOrg, sharedOrg, token, refreshToken }`.\n\nA wrong password and an unknown account produce the **same** error, deliberately — distinguishing them would let an attacker enumerate accounts. Do not add a more helpful message on the client.\n\nRepeated failures can lock the account, and a blocked device is refused before credentials are even checked.\n\n#### Signature\n\n```http\nPOST /profile/signin (body) -> A session, or the next step to take\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit this endpoint — the error deliberately gives an attacker nothing, but attempts are still cheap.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/refresh`\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":false,"in":"header","description":"The org to sign into. Omit it to let the email find the org (see the description).","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string"},"password":{"type":"string","format":"password"}}},"example":{"email":"ada@example.com","password":"correct horse battery staple"}}}},"responses":{"201":{"description":"A session, or the next step to take","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}},"examples":{"signedIn":{"summary":"Signed in","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","orgId":"acme","user":{"sk":"65f0c2a1e4b0a1b2c3d4e5f6","data":{"email":"ada@example.com"}}}},"secondFactor":{"summary":"Second factor required","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","message":"Verification code sent to your email"}},"newDevice":{"summary":"New device (when the org verifies new devices)","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","isNewDevice":true,"message":"New device detected. Verification code sent to your email."}},"temporaryPassword":{"summary":"Temporary password — change it now","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","requiresPasswordChange":true,"message":"Temporary password used. Please set a new password.","userId":"65f0c2a1e4b0a1b2c3d4e5f6","email":"ada@example.com"}},"chooseOrg":{"summary":"No orgid sent, several orgs match","value":{"requiresOrgChoice":true,"orgs":[{"orgId":"acme","displayName":"Acme"},{"orgId":"acme-eu","displayName":"Acme EU"}]}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/profile/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/profile/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/signin":{"post":{"operationId":"UsersController_signin","summary":"Sign in","description":"Authenticates a user and issues tokens.\n\n**Without an `orgid` header** the email finds the org through the account directory: when the password matches in exactly one org the session is issued for it (with `orgName`); when it matches in several the answer is `{ requiresOrgChoice: true, orgs }` and the client signs in again with the chosen `orgid`.\n\nThe answer may be a step rather than a session: a second-factor challenge (`requiresTwoFactor`, `challengeToken`, `twoFactorMethod` — the code is already sent unless the method is `authenticator`), a new-device check (`isNewDevice: true`, when the org turns on new-device verification), or tokens with `requiresPasswordChange: true` after a temporary password. Otherwise it is `{ user, orgId, rootOrg, sharedOrg, token, refreshToken }`.\n\nA wrong password and an unknown account produce the **same** error, deliberately — distinguishing them would let an attacker enumerate accounts. Do not add a more helpful message on the client.\n\nRepeated failures can lock the account, and a blocked device is refused before credentials are even checked.\n\n#### Signature\n\n```http\nPOST /user/user/signin (body) -> A session, or the next step to take\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit this endpoint — the error deliberately gives an attacker nothing, but attempts are still cheap.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/refresh`\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":false,"in":"header","description":"The org to sign into. Omit it to let the email find the org (see the description).","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string"},"password":{"type":"string","format":"password"}}},"example":{"email":"ada@example.com","password":"correct horse battery staple"}}}},"responses":{"201":{"description":"A session, or the next step to take","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}},"examples":{"signedIn":{"summary":"Signed in","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","orgId":"acme","user":{"sk":"65f0c2a1e4b0a1b2c3d4e5f6","data":{"email":"ada@example.com"}}}},"secondFactor":{"summary":"Second factor required","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","message":"Verification code sent to your email"}},"newDevice":{"summary":"New device (when the org verifies new devices)","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","isNewDevice":true,"message":"New device detected. Verification code sent to your email."}},"temporaryPassword":{"summary":"Temporary password — change it now","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","requiresPasswordChange":true,"message":"Temporary password used. Please set a new password.","userId":"65f0c2a1e4b0a1b2c3d4e5f6","email":"ada@example.com"}},"chooseOrg":{"summary":"No orgid sent, several orgs match","value":{"requiresOrgChoice":true,"orgs":[{"orgId":"acme","displayName":"Acme"},{"orgId":"acme-eu","displayName":"Acme EU"}]}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/user/user/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/user/user/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/signin":{"post":{"operationId":"UsersController_signin","summary":"Sign in","description":"Authenticates a user and issues tokens.\n\n**Without an `orgid` header** the email finds the org through the account directory: when the password matches in exactly one org the session is issued for it (with `orgName`); when it matches in several the answer is `{ requiresOrgChoice: true, orgs }` and the client signs in again with the chosen `orgid`.\n\nThe answer may be a step rather than a session: a second-factor challenge (`requiresTwoFactor`, `challengeToken`, `twoFactorMethod` — the code is already sent unless the method is `authenticator`), a new-device check (`isNewDevice: true`, when the org turns on new-device verification), or tokens with `requiresPasswordChange: true` after a temporary password. Otherwise it is `{ user, orgId, rootOrg, sharedOrg, token, refreshToken }`.\n\nA wrong password and an unknown account produce the **same** error, deliberately — distinguishing them would let an attacker enumerate accounts. Do not add a more helpful message on the client.\n\nRepeated failures can lock the account, and a blocked device is refused before credentials are even checked.\n\n#### Signature\n\n```http\nPOST /user/signin (body) -> A session, or the next step to take\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit this endpoint — the error deliberately gives an attacker nothing, but attempts are still cheap.\n- Access gate: when an active requirement rule has `enforcement.access` block/override with `gatedRoles`/`gatedGroups` this person holds, and the requirement is unmet with no active override, those roles (and every role those groups grant) are withheld from the resolved grants. `user.data.readinessWithheld = { roles[], groups[], reasons[] }` says why. Root* grants are never withheld; root-org protection is unchanged.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/refresh`\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":false,"in":"header","description":"The org to sign into. Omit it to let the email find the org (see the description).","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string"},"password":{"type":"string","format":"password"}}},"example":{"email":"ada@example.com","password":"correct horse battery staple"}}}},"responses":{"201":{"description":"A session, or the next step to take","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}},"examples":{"signedIn":{"summary":"Signed in","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","orgId":"acme","user":{"sk":"65f0c2a1e4b0a1b2c3d4e5f6","data":{"email":"ada@example.com"}}}},"secondFactor":{"summary":"Second factor required","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","message":"Verification code sent to your email"}},"newDevice":{"summary":"New device (when the org verifies new devices)","value":{"requiresTwoFactor":true,"challengeToken":"chl_9k2m4h1p7q","twoFactorMethod":"email","isNewDevice":true,"message":"New device detected. Verification code sent to your email."}},"temporaryPassword":{"summary":"Temporary password — change it now","value":{"token":"eyJhbGciOi…","refreshToken":"eyJhbGciOi…","requiresPasswordChange":true,"message":"Temporary password used. Please set a new password.","userId":"65f0c2a1e4b0a1b2c3d4e5f6","email":"ada@example.com"}},"chooseOrg":{"summary":"No orgid sent, several orgs match","value":{"requiresOrgChoice":true,"orgs":[{"orgId":"acme","displayName":"Acme"},{"orgId":"acme-eu","displayName":"Acme EU"}]}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/user/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/user/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/signin/passcode":{"post":{"operationId":"UsersController_signinPasscode","summary":"Sign in with a passcode","description":"Authenticates with a numeric passcode rather than a password — the shop-floor and shared-terminal flow, where typing a long password on a till is impractical.\n\n#### Signature\n\n```http\nPOST /profile/user/signin/passcode (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A six-digit passcode is far weaker than a password — pair it with device restrictions.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /passcode/set`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Passcode credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/profile/user/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/profile/user/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/signin/passcode":{"post":{"operationId":"UsersController_signinPasscode","summary":"Sign in with a passcode","description":"Authenticates with a numeric passcode rather than a password — the shop-floor and shared-terminal flow, where typing a long password on a till is impractical.\n\n#### Signature\n\n```http\nPOST /profile/signin/passcode (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A six-digit passcode is far weaker than a password — pair it with device restrictions.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /passcode/set`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Passcode credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/profile/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/profile/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/signin/passcode":{"post":{"operationId":"UsersController_signinPasscode","summary":"Sign in with a passcode","description":"Authenticates with a numeric passcode rather than a password — the shop-floor and shared-terminal flow, where typing a long password on a till is impractical.\n\n#### Signature\n\n```http\nPOST /user/user/signin/passcode (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A six-digit passcode is far weaker than a password — pair it with device restrictions.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /passcode/set`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Passcode credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/user/user/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/user/user/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/signin/passcode":{"post":{"operationId":"UsersController_signinPasscode","summary":"Sign in with a passcode","description":"Authenticates with a numeric passcode rather than a password — the shop-floor and shared-terminal flow, where typing a long password on a till is impractical.\n\n#### Signature\n\n```http\nPOST /user/signin/passcode (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A six-digit passcode is far weaker than a password — pair it with device restrictions.\n- Same as POST /signin/passcode: `surface: \"pos\"` applies the POS readiness gate.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n| `423` | READINESS_BLOCK | <name> can't sign in to the POS: <requirement titles>. | A requirement rule whose `enforcement.pos` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /passcode/set`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Passcode credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/user/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/user/signin/passcode","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't sign in to the POS: <requirement titles>. — A requirement rule whose `enforcement.pos` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"pos","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/passcode/set":{"post":{"operationId":"UsersController_setPasscode","summary":"Set a passcode","description":"Sets a numeric passcode for terminal sign-in. **Must be exactly six digits** — the check is strict, so a shorter or non-numeric value is refused.\n\n#### Signature\n\n```http\nPOST /profile/passcode/set (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSCODE | Passcode must be exactly 6 digits | The passcode is not six digits. | Exactly six numeric digits. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The passcode.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Passcode must be exactly 6 digits — The passcode is not six digits.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Passcode must be exactly 6 digits","path":"/profile/passcode/set","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/passcode/set":{"post":{"operationId":"UsersController_setPasscode","summary":"Set a passcode","description":"Sets a numeric passcode for terminal sign-in. **Must be exactly six digits** — the check is strict, so a shorter or non-numeric value is refused.\n\n#### Signature\n\n```http\nPOST /profile/user/passcode/set (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSCODE | Passcode must be exactly 6 digits | The passcode is not six digits. | Exactly six numeric digits. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The passcode.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Passcode must be exactly 6 digits — The passcode is not six digits.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Passcode must be exactly 6 digits","path":"/profile/user/passcode/set","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/passcode/set":{"post":{"operationId":"UsersController_setPasscode","summary":"Set a passcode","description":"Sets a numeric passcode for terminal sign-in. **Must be exactly six digits** — the check is strict, so a shorter or non-numeric value is refused.\n\n#### Signature\n\n```http\nPOST /user/passcode/set (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSCODE | Passcode must be exactly 6 digits | The passcode is not six digits. | Exactly six numeric digits. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The passcode.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Passcode must be exactly 6 digits — The passcode is not six digits.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Passcode must be exactly 6 digits","path":"/user/passcode/set","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/passcode/set":{"post":{"operationId":"UsersController_setPasscode","summary":"Set a passcode","description":"Sets a numeric passcode for terminal sign-in. **Must be exactly six digits** — the check is strict, so a shorter or non-numeric value is refused.\n\n#### Signature\n\n```http\nPOST /user/user/passcode/set (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSCODE | Passcode must be exactly 6 digits | The passcode is not six digits. | Exactly six numeric digits. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /signin/passcode`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The passcode.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","passcode":"481625"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Passcode must be exactly 6 digits — The passcode is not six digits.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Passcode must be exactly 6 digits","path":"/user/user/passcode/set","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/passcode/remove":{"post":{"operationId":"UsersController_removePasscode","summary":"Remove a passcode","description":"Removes a passcode, disabling terminal sign-in for that employee. Do this as part of offboarding.\n\n#### Signature\n\n```http\nPOST /profile/passcode/remove (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/revoke-access`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Whose passcode to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/passcode/remove":{"post":{"operationId":"UsersController_removePasscode","summary":"Remove a passcode","description":"Removes a passcode, disabling terminal sign-in for that employee. Do this as part of offboarding.\n\n#### Signature\n\n```http\nPOST /profile/user/passcode/remove (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/revoke-access`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Whose passcode to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/passcode/remove":{"post":{"operationId":"UsersController_removePasscode","summary":"Remove a passcode","description":"Removes a passcode, disabling terminal sign-in for that employee. Do this as part of offboarding.\n\n#### Signature\n\n```http\nPOST /user/passcode/remove (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/revoke-access`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Whose passcode to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/passcode/remove":{"post":{"operationId":"UsersController_removePasscode","summary":"Remove a passcode","description":"Removes a passcode, disabling terminal sign-in for that employee. Do this as part of offboarding.\n\n#### Signature\n\n```http\nPOST /user/user/passcode/remove (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/revoke-access`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Whose passcode to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/passcode/card/register":{"post":{"operationId":"UsersController_registerCard","summary":"Register a passcode card","description":"Registers a physical card or fob for terminal sign-in.\n\n#### Signature\n\n```http\nPOST /profile/passcode/card/register (body) -> The registered card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","cardIdentifier":"CARD-9K2M4H"}}}},"responses":{"201":{"description":"The registered card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/passcode/card/register":{"post":{"operationId":"UsersController_registerCard","summary":"Register a passcode card","description":"Registers a physical card or fob for terminal sign-in.\n\n#### Signature\n\n```http\nPOST /profile/user/passcode/card/register (body) -> The registered card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","cardIdentifier":"CARD-9K2M4H"}}}},"responses":{"201":{"description":"The registered card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/passcode/card/register":{"post":{"operationId":"UsersController_registerCard","summary":"Register a passcode card","description":"Registers a physical card or fob for terminal sign-in.\n\n#### Signature\n\n```http\nPOST /user/passcode/card/register (body) -> The registered card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","cardIdentifier":"CARD-9K2M4H"}}}},"responses":{"201":{"description":"The registered card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/passcode/card/register":{"post":{"operationId":"UsersController_registerCard","summary":"Register a passcode card","description":"Registers a physical card or fob for terminal sign-in.\n\n#### Signature\n\n```http\nPOST /user/user/passcode/card/register (body) -> The registered card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","cardIdentifier":"CARD-9K2M4H"}}}},"responses":{"201":{"description":"The registered card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/passcode/card/status":{"post":{"operationId":"UsersController_setCardStatus","summary":"Set a card status","description":"Enables or disables a registered card. Disabling is the immediate response to a lost card — faster and more reversible than deleting it.\n\n#### Signature\n\n```http\nPOST /profile/passcode/card/status (body) -> The updated card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/card/{identifier}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card and its new status.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"cardIdentifier":"CARD-9K2M4H","status":"disabled"}}}},"responses":{"201":{"description":"The updated card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/passcode/card/status":{"post":{"operationId":"UsersController_setCardStatus","summary":"Set a card status","description":"Enables or disables a registered card. Disabling is the immediate response to a lost card — faster and more reversible than deleting it.\n\n#### Signature\n\n```http\nPOST /profile/user/passcode/card/status (body) -> The updated card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/card/{identifier}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card and its new status.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"cardIdentifier":"CARD-9K2M4H","status":"disabled"}}}},"responses":{"201":{"description":"The updated card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/passcode/card/status":{"post":{"operationId":"UsersController_setCardStatus","summary":"Set a card status","description":"Enables or disables a registered card. Disabling is the immediate response to a lost card — faster and more reversible than deleting it.\n\n#### Signature\n\n```http\nPOST /user/passcode/card/status (body) -> The updated card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/card/{identifier}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card and its new status.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"cardIdentifier":"CARD-9K2M4H","status":"disabled"}}}},"responses":{"201":{"description":"The updated card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/passcode/card/status":{"post":{"operationId":"UsersController_setCardStatus","summary":"Set a card status","description":"Enables or disables a registered card. Disabling is the immediate response to a lost card — faster and more reversible than deleting it.\n\n#### Signature\n\n```http\nPOST /user/user/passcode/card/status (body) -> The updated card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/card/{identifier}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The card and its new status.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"cardIdentifier":"CARD-9K2M4H","status":"disabled"}}}},"responses":{"201":{"description":"The updated card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/passcode/cards/{employeeId}":{"get":{"operationId":"UsersController_listCards","summary":"List an employee's cards","description":"The cards registered to an employee. Omitting the segment lists cards more broadly.\n\n#### Signature\n\n```http\nGET /profile/passcode/cards/{employeeId} (employeeId: string) -> Registered cards\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/register`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","description":"Employee id. Optional segment.","schema":{"type":"string"},"example":"E-00412"}],"responses":{"200":{"description":"Registered cards","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/passcode/cards/{employeeId}":{"get":{"operationId":"UsersController_listCards","summary":"List an employee's cards","description":"The cards registered to an employee. Omitting the segment lists cards more broadly.\n\n#### Signature\n\n```http\nGET /profile/user/passcode/cards/{employeeId} (employeeId: string) -> Registered cards\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/register`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","description":"Employee id. Optional segment.","schema":{"type":"string"},"example":"E-00412"}],"responses":{"200":{"description":"Registered cards","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/passcode/cards/{employeeId}":{"get":{"operationId":"UsersController_listCards","summary":"List an employee's cards","description":"The cards registered to an employee. Omitting the segment lists cards more broadly.\n\n#### Signature\n\n```http\nGET /user/passcode/cards/{employeeId} (employeeId: string) -> Registered cards\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/register`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","description":"Employee id. Optional segment.","schema":{"type":"string"},"example":"E-00412"}],"responses":{"200":{"description":"Registered cards","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/passcode/cards/{employeeId}":{"get":{"operationId":"UsersController_listCards","summary":"List an employee's cards","description":"The cards registered to an employee. Omitting the segment lists cards more broadly.\n\n#### Signature\n\n```http\nGET /user/user/passcode/cards/{employeeId} (employeeId: string) -> Registered cards\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /passcode/card/register`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","description":"Employee id. Optional segment.","schema":{"type":"string"},"example":"E-00412"}],"responses":{"200":{"description":"Registered cards","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/passcode/card/{identifier}":{"get":{"operationId":"UsersController_getPasscodeCardInfo","summary":"Get card information","description":"Looks up a registered card by its identifier — what a terminal does when a card is presented.\n\n#### Signature\n\n```http\nGET /profile/passcode/card/{identifier} (identifier: string) -> The card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/cards/{employeeId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"identifier","required":true,"in":"path","description":"Card identifier.","schema":{"type":"string"},"example":"CARD-9K2M4H"}],"responses":{"200":{"description":"The card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/passcode/card/{identifier}":{"get":{"operationId":"UsersController_getPasscodeCardInfo","summary":"Get card information","description":"Looks up a registered card by its identifier — what a terminal does when a card is presented.\n\n#### Signature\n\n```http\nGET /profile/user/passcode/card/{identifier} (identifier: string) -> The card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/cards/{employeeId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"identifier","required":true,"in":"path","description":"Card identifier.","schema":{"type":"string"},"example":"CARD-9K2M4H"}],"responses":{"200":{"description":"The card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/passcode/card/{identifier}":{"get":{"operationId":"UsersController_getPasscodeCardInfo","summary":"Get card information","description":"Looks up a registered card by its identifier — what a terminal does when a card is presented.\n\n#### Signature\n\n```http\nGET /user/passcode/card/{identifier} (identifier: string) -> The card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/cards/{employeeId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"identifier","required":true,"in":"path","description":"Card identifier.","schema":{"type":"string"},"example":"CARD-9K2M4H"}],"responses":{"200":{"description":"The card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/passcode/card/{identifier}":{"get":{"operationId":"UsersController_getPasscodeCardInfo","summary":"Get card information","description":"Looks up a registered card by its identifier — what a terminal does when a card is presented.\n\n#### Signature\n\n```http\nGET /user/user/passcode/card/{identifier} (identifier: string) -> The card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /passcode/cards/{employeeId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"identifier","required":true,"in":"path","description":"Card identifier.","schema":{"type":"string"},"example":"CARD-9K2M4H"}],"responses":{"200":{"description":"The card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/employee/candidates/{userId}":{"get":{"operationId":"UsersController_employeeCandidatesForUser","summary":"Get employee candidates for a user","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"User id.","example":"USR-4821"}],"responses":{"200":{"description":"Candidate employees","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Suggests employee records that might correspond to a user account — the matching aid when linking the two.\n\n#### Signature\n\n```http\nGET /profile/user/employee/candidates/{userId} (userId: string) -> Candidate employees\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/employee/link`"}},"/user/user/employee/candidates/{userId}":{"get":{"operationId":"UsersController_employeeCandidatesForUser","summary":"Get employee candidates for a user","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"User id.","example":"USR-4821"}],"responses":{"200":{"description":"Candidate employees","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Suggests employee records that might correspond to a user account — the matching aid when linking the two.\n\n#### Signature\n\n```http\nGET /user/user/employee/candidates/{userId} (userId: string) -> Candidate employees\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/employee/link`"}},"/profile/user/employee/{employeeId}/user-candidates":{"get":{"operationId":"UsersController_userCandidatesForEmployee","summary":"Get user candidates for an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"Candidate users","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"The reverse lookup: user accounts that might correspond to an employee record.\n\n#### Signature\n\n```http\nGET /profile/user/employee/{employeeId}/user-candidates (employeeId: string) -> Candidate users\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/employee/link`"}},"/user/user/employee/{employeeId}/user-candidates":{"get":{"operationId":"UsersController_userCandidatesForEmployee","summary":"Get user candidates for an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"Candidate users","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"The reverse lookup: user accounts that might correspond to an employee record.\n\n#### Signature\n\n```http\nGET /user/user/employee/{employeeId}/user-candidates (employeeId: string) -> Candidate users\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/employee/link`"}},"/profile/user/employee/link":{"post":{"operationId":"UsersController_linkUserEmployee","summary":"Link a user to an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The user and employee to link.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"USR-4821","employeeId":"EMP-4821"}}}},"responses":{"201":{"description":"The link result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Connects a login account to an HR employee record, so the same person is one identity across both.\n\n#### Signature\n\n```http\nPOST /profile/user/employee/link (body) -> The link result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /user/employee/candidates/{userId}`"}},"/user/user/employee/link":{"post":{"operationId":"UsersController_linkUserEmployee","summary":"Link a user to an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The user and employee to link.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"USR-4821","employeeId":"EMP-4821"}}}},"responses":{"201":{"description":"The link result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Connects a login account to an HR employee record, so the same person is one identity across both.\n\n#### Signature\n\n```http\nPOST /user/user/employee/link (body) -> The link result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /user/employee/candidates/{userId}`"}},"/profile/user/employee/create-from-user":{"post":{"operationId":"UsersController_createEmployeeFromUser","summary":"Create an employee from a user","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The user to base it on.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"USR-4821"}}}},"responses":{"201":{"description":"The created employee","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Creates an HR employee record from an existing login account, carrying the details across.\n\n#### Signature\n\n```http\nPOST /profile/user/employee/create-from-user (body) -> The created employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/employee/create-from-employee`"}},"/user/user/employee/create-from-user":{"post":{"operationId":"UsersController_createEmployeeFromUser","summary":"Create an employee from a user","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The user to base it on.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"USR-4821"}}}},"responses":{"201":{"description":"The created employee","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Creates an HR employee record from an existing login account, carrying the details across.\n\n#### Signature\n\n```http\nPOST /user/user/employee/create-from-user (body) -> The created employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/employee/create-from-employee`"}},"/profile/user/employee/create-from-employee":{"post":{"operationId":"UsersController_createUserFromEmployee","summary":"Create a user from an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The employee to base it on.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821"}}}},"responses":{"201":{"description":"The created user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Creates a login account for an existing employee — the step that gives a new hire access once their HR record exists.\n\n#### Signature\n\n```http\nPOST /profile/user/employee/create-from-employee (body) -> The created user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/send`"}},"/user/user/employee/create-from-employee":{"post":{"operationId":"UsersController_createUserFromEmployee","summary":"Create a user from an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The employee to base it on.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821"}}}},"responses":{"201":{"description":"The created user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Creates a login account for an existing employee — the step that gives a new hire access once their HR record exists.\n\n#### Signature\n\n```http\nPOST /user/user/employee/create-from-employee (body) -> The created user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/send`"}},"/profile/user/refresh":{"post":{"operationId":"UsersController_refreshToken","summary":"Refresh an access token","description":"Exchanges a refresh token for a new access token. The refresh token is longer-lived and therefore the more valuable credential — store it more carefully than the access token.\n\n#### Signature\n\n```http\nPOST /profile/user/refresh (body) -> New tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The refresh token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"refreshToken":"eyJhbGciOi…"}}}},"responses":{"201":{"description":"New tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/refresh":{"post":{"operationId":"UsersController_refreshToken","summary":"Refresh token","description":"Refreshes an authentication token","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization ID","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Refresh token details","content":{"application/json":{"schema":{"type":"object","required":["refresh_token"],"properties":{"refresh_token":{"type":"string","description":"Refresh token"}}}}}},"responses":{"200":{"description":"Token successfully refreshed"}},"tags":["Users"]}},"/user/user/refresh":{"post":{"operationId":"UsersController_refreshToken","summary":"Refresh an access token","description":"Exchanges a refresh token for a new access token. The refresh token is longer-lived and therefore the more valuable credential — store it more carefully than the access token.\n\n#### Signature\n\n```http\nPOST /user/user/refresh (body) -> New tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The refresh token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"refreshToken":"eyJhbGciOi…"}}}},"responses":{"201":{"description":"New tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/refresh":{"post":{"operationId":"UsersController_refreshToken","summary":"Refresh token","description":"Refreshes an authentication token","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization ID","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Refresh token details","content":{"application/json":{"schema":{"type":"object","required":["refresh_token"],"properties":{"refresh_token":{"type":"string","description":"Refresh token"}}}}}},"responses":{"200":{"description":"Token successfully refreshed"}},"tags":["Users"]}},"/profile/user/meta":{"post":{"operationId":"UsersController_updateMyMeta","summary":"Update my metadata","description":"Writes arbitrary metadata against the caller's account, namespaced so different features do not collide. `mode` chooses whether the value merges into what is there or replaces it outright.\n\n#### Signature\n\n```http\nPOST /profile/user/meta (body) -> The updated metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `mode: \"replace\"` discards everything else in the namespace.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAMESPACE_REQUIRED | A valid meta namespace is required | `namespace` is missing or invalid. | Namespaces keep features from overwriting each other. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/{userId}/meta`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The metadata to write.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["namespace","value"],"properties":{"namespace":{"type":"string","description":"Namespace to write under.","example":"preferences"},"value":{"type":"object","additionalProperties":true,"description":"Must be an object."},"mode":{"type":"string","enum":["merge","replace"],"default":"merge","example":"merge"}}},"example":{"namespace":"preferences","value":{"theme":"dark"},"mode":"merge"}}}},"responses":{"201":{"description":"The updated metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid meta namespace is required — `namespace` is missing or invalid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid meta namespace is required","path":"/profile/user/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"]}},"/profile/meta":{"post":{"operationId":"UsersController_updateMyMeta","summary":"Update my metadata","description":"Writes arbitrary metadata against the caller's account, namespaced so different features do not collide. `mode` chooses whether the value merges into what is there or replaces it outright.\n\n#### Signature\n\n```http\nPOST /profile/meta (body) -> The updated metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `mode: \"replace\"` discards everything else in the namespace.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAMESPACE_REQUIRED | A valid meta namespace is required | `namespace` is missing or invalid. | Namespaces keep features from overwriting each other. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/{userId}/meta`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The metadata to write.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["namespace","value"],"properties":{"namespace":{"type":"string","description":"Namespace to write under.","example":"preferences"},"value":{"type":"object","additionalProperties":true,"description":"Must be an object."},"mode":{"type":"string","enum":["merge","replace"],"default":"merge","example":"merge"}}},"example":{"namespace":"preferences","value":{"theme":"dark"},"mode":"merge"}}}},"responses":{"201":{"description":"The updated metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid meta namespace is required — `namespace` is missing or invalid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid meta namespace is required","path":"/profile/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"]}},"/user/user/meta":{"post":{"operationId":"UsersController_updateMyMeta","summary":"Update my metadata","description":"Writes arbitrary metadata against the caller's account, namespaced so different features do not collide. `mode` chooses whether the value merges into what is there or replaces it outright.\n\n#### Signature\n\n```http\nPOST /user/user/meta (body) -> The updated metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `mode: \"replace\"` discards everything else in the namespace.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAMESPACE_REQUIRED | A valid meta namespace is required | `namespace` is missing or invalid. | Namespaces keep features from overwriting each other. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/{userId}/meta`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The metadata to write.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["namespace","value"],"properties":{"namespace":{"type":"string","description":"Namespace to write under.","example":"preferences"},"value":{"type":"object","additionalProperties":true,"description":"Must be an object."},"mode":{"type":"string","enum":["merge","replace"],"default":"merge","example":"merge"}}},"example":{"namespace":"preferences","value":{"theme":"dark"},"mode":"merge"}}}},"responses":{"201":{"description":"The updated metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid meta namespace is required — `namespace` is missing or invalid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid meta namespace is required","path":"/user/user/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"]}},"/user/meta":{"post":{"operationId":"UsersController_updateMyMeta","summary":"Update my metadata","description":"Writes arbitrary metadata against the caller's account, namespaced so different features do not collide. `mode` chooses whether the value merges into what is there or replaces it outright.\n\n#### Signature\n\n```http\nPOST /user/meta (body) -> The updated metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `mode: \"replace\"` discards everything else in the namespace.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAMESPACE_REQUIRED | A valid meta namespace is required | `namespace` is missing or invalid. | Namespaces keep features from overwriting each other. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/{userId}/meta`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The metadata to write.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["namespace","value"],"properties":{"namespace":{"type":"string","description":"Namespace to write under.","example":"preferences"},"value":{"type":"object","additionalProperties":true,"description":"Must be an object."},"mode":{"type":"string","enum":["merge","replace"],"default":"merge","example":"merge"}}},"example":{"namespace":"preferences","value":{"theme":"dark"},"mode":"merge"}}}},"responses":{"201":{"description":"The updated metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid meta namespace is required — `namespace` is missing or invalid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid meta namespace is required","path":"/user/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"]}},"/profile/user/{userId}/meta":{"post":{"operationId":"UsersController_updateUserMetaAsAdmin","summary":"Update another user's metadata","description":"Writes metadata against another user's account. Admin-only, unlike the self-service form.\n\n#### Signature\n\n```http\nPOST /profile/user/{userId}/meta (userId: string, body) -> The updated metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | ADMIN_ONLY | Only an admin can <action> | The caller lacks the admin rights the action requires. | The message names the action that was refused. |\n| `400` | USER_ID_REQUIRED | User id required | The user id is missing. | Supply it in the path. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /meta`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"userId","required":true,"in":"path","description":"User id.","schema":{"type":"string"},"example":"USR-4821"}],"responses":{"201":{"description":"The updated metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"User id required — The user id is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User id required","path":"/profile/user/{userId}/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an admin can <action> — The caller lacks the admin rights the action requires.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an admin can <action>","path":"/profile/user/{userId}/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"requestBody":{"description":"The metadata to write.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["namespace","value"],"properties":{"namespace":{"type":"string","example":"preferences"},"value":{"type":"object","additionalProperties":true},"mode":{"type":"string","enum":["merge","replace"],"example":"merge"}}},"example":{"namespace":"preferences","value":{"theme":"dark"}}}}}}},"/user/user/{userId}/meta":{"post":{"operationId":"UsersController_updateUserMetaAsAdmin","summary":"Update another user's metadata","description":"Writes metadata against another user's account. Admin-only, unlike the self-service form.\n\n#### Signature\n\n```http\nPOST /user/user/{userId}/meta (userId: string, body) -> The updated metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | ADMIN_ONLY | Only an admin can <action> | The caller lacks the admin rights the action requires. | The message names the action that was refused. |\n| `400` | USER_ID_REQUIRED | User id required | The user id is missing. | Supply it in the path. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /meta`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"userId","required":true,"in":"path","description":"User id.","schema":{"type":"string"},"example":"USR-4821"}],"responses":{"201":{"description":"The updated metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"User id required — The user id is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User id required","path":"/user/user/{userId}/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an admin can <action> — The caller lacks the admin rights the action requires.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an admin can <action>","path":"/user/user/{userId}/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"requestBody":{"description":"The metadata to write.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["namespace","value"],"properties":{"namespace":{"type":"string","example":"preferences"},"value":{"type":"object","additionalProperties":true},"mode":{"type":"string","enum":["merge","replace"],"example":"merge"}}},"example":{"namespace":"preferences","value":{"theme":"dark"}}}}}}},"/profile/signup":{"post":{"operationId":"UsersController_signup","summary":"Sign up","description":"Registers a new user in the organization. An address already registered is refused rather than silently creating a duplicate.\n\n#### Signature\n\n```http\nPOST /profile/signup (body) -> The created user, usually with tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The error confirms whether an address is registered, unlike sign-in. That is an enumeration surface — rate-limit it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_EXISTS | A user with this email already exists in the organization | The email is already registered. | Sign in instead, or use the password reset flow. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`\n- `POST /user/invite/send`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The account to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"correct horse battery staple","firstName":"Ada","lastName":"Lovelace"}}}},"responses":{"201":{"description":"The created user, usually with tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"A user with this email already exists in the organization — The email is already registered.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A user with this email already exists in the organization","path":"/profile/signup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/signup":{"post":{"operationId":"UsersController_signup","summary":"Sign up","description":"Registers a new user in the organization. An address already registered is refused rather than silently creating a duplicate.\n\n#### Signature\n\n```http\nPOST /profile/user/signup (body) -> The created user, usually with tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The error confirms whether an address is registered, unlike sign-in. That is an enumeration surface — rate-limit it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_EXISTS | A user with this email already exists in the organization | The email is already registered. | Sign in instead, or use the password reset flow. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`\n- `POST /user/invite/send`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The account to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"correct horse battery staple","firstName":"Ada","lastName":"Lovelace"}}}},"responses":{"201":{"description":"The created user, usually with tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"A user with this email already exists in the organization — The email is already registered.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A user with this email already exists in the organization","path":"/profile/user/signup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/signup":{"post":{"operationId":"UsersController_signup","summary":"Sign up","description":"Registers a new user in the organization. An address already registered is refused rather than silently creating a duplicate.\n\n#### Signature\n\n```http\nPOST /user/signup (body) -> The created user, usually with tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The error confirms whether an address is registered, unlike sign-in. That is an enumeration surface — rate-limit it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_EXISTS | A user with this email already exists in the organization | The email is already registered. | Sign in instead, or use the password reset flow. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`\n- `POST /user/invite/send`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The account to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"correct horse battery staple","firstName":"Ada","lastName":"Lovelace"}}}},"responses":{"201":{"description":"The created user, usually with tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"A user with this email already exists in the organization — The email is already registered.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A user with this email already exists in the organization","path":"/user/signup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/signup":{"post":{"operationId":"UsersController_signup","summary":"Sign up","description":"Registers a new user in the organization. An address already registered is refused rather than silently creating a duplicate.\n\n#### Signature\n\n```http\nPOST /user/user/signup (body) -> The created user, usually with tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The error confirms whether an address is registered, unlike sign-in. That is an enumeration surface — rate-limit it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_EXISTS | A user with this email already exists in the organization | The email is already registered. | Sign in instead, or use the password reset flow. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`\n- `POST /user/invite/send`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The account to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"correct horse battery staple","firstName":"Ada","lastName":"Lovelace"}}}},"responses":{"201":{"description":"The created user, usually with tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"A user with this email already exists in the organization — The email is already registered.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A user with this email already exists in the organization","path":"/user/user/signup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/logout/{userId}":{"get":{"operationId":"UsersController_logout","summary":"Log a user out","description":"Ends a user's session by id. Note this is a `GET` that changes state, so it can be triggered by anything following a link.\n\n#### Signature\n\n```http\nGET /profile/logout/{userId} (userId: string) -> Logout result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- State change on `GET` — do not place behind a prefetchable link.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signout`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"userId","required":true,"in":"path","description":"User id or email.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"Logout result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/logout/{userId}":{"get":{"operationId":"UsersController_logout","summary":"Log a user out","description":"Ends a user's session by id. Note this is a `GET` that changes state, so it can be triggered by anything following a link.\n\n#### Signature\n\n```http\nGET /profile/user/logout/{userId} (userId: string) -> Logout result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- State change on `GET` — do not place behind a prefetchable link.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signout`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"userId","required":true,"in":"path","description":"User id or email.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"Logout result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/logout/{userId}":{"get":{"operationId":"UsersController_logout","summary":"Log a user out","description":"Ends a user's session by id. Note this is a `GET` that changes state, so it can be triggered by anything following a link.\n\n#### Signature\n\n```http\nGET /user/logout/{userId} (userId: string) -> Logout result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- State change on `GET` — do not place behind a prefetchable link.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signout`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"userId","required":true,"in":"path","description":"User id or email.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"Logout result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/logout/{userId}":{"get":{"operationId":"UsersController_logout","summary":"Log a user out","description":"Ends a user's session by id. Note this is a `GET` that changes state, so it can be triggered by anything following a link.\n\n#### Signature\n\n```http\nGET /user/user/logout/{userId} (userId: string) -> Logout result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- State change on `GET` — do not place behind a prefetchable link.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signout`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"userId","required":true,"in":"path","description":"User id or email.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"Logout result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/password/change":{"post":{"operationId":"UsersController_passwordChange","summary":"Change a password","description":"Any signed-in person may call it. **Below ConfigAdmin** you change your own password only: a `userId` other than your own `sk` is refused (403); omit it to mean yourself. Your current password is required unless the account is on a temporary password. The password policy that governs you (see *Password policies* below) must allow self-service change — except a temporary-password account finishing its forced first change, which is always allowed — and the new password must meet that policy's length and complexity rules. **ConfigAdmin and above** change anyone's password as before, without the policy switch. Success clears `temporaryPassword`.\n\n#### Signature\n\n```http\nPOST /profile/password/change (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Password policies: a `passwordpolicy` record carries `allowSelfServiceChange` / `allowSelfServiceReset` (both default on) and the rules (`minLenght`, `minLowercase`, `minUppercase`, `minNumbers`, `minSymbols`, `exclude`, `pattern`). A user, group or role points at one through `data.passwordPolicy` (the policy name); a policy with `isDefault: true` covers everyone else. Precedence: user > group > role > org default; within a level the first by name. **No policy applies = full self-service, no rules.**\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSWORD | password not correct | The current password is wrong. | The change is refused; the existing password stands. |\n| `403` | NOT_YOURS | You can only change your own password | A non-admin named someone else's `userId`. | Ask an administrator. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`\n- `GET /user/password-policy/assignments`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Target (optional, `sk`), current and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","password":"…","newPassword":"…"}}}},"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"password not correct — The current password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"password not correct","path":"/profile/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You can only change your own password — A non-admin named someone else's `userId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You can only change your own password","path":"/profile/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/password/change":{"post":{"operationId":"UsersController_passwordChange","summary":"Change a password","description":"Any signed-in person may call it. **Below ConfigAdmin** you change your own password only: a `userId` other than your own `sk` is refused (403); omit it to mean yourself. Your current password is required unless the account is on a temporary password. The password policy that governs you (see *Password policies* below) must allow self-service change — except a temporary-password account finishing its forced first change, which is always allowed — and the new password must meet that policy's length and complexity rules. **ConfigAdmin and above** change anyone's password as before, without the policy switch. Success clears `temporaryPassword`.\n\n#### Signature\n\n```http\nPOST /profile/user/password/change (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Password policies: a `passwordpolicy` record carries `allowSelfServiceChange` / `allowSelfServiceReset` (both default on) and the rules (`minLenght`, `minLowercase`, `minUppercase`, `minNumbers`, `minSymbols`, `exclude`, `pattern`). A user, group or role points at one through `data.passwordPolicy` (the policy name); a policy with `isDefault: true` covers everyone else. Precedence: user > group > role > org default; within a level the first by name. **No policy applies = full self-service, no rules.**\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSWORD | password not correct | The current password is wrong. | The change is refused; the existing password stands. |\n| `403` | NOT_YOURS | You can only change your own password | A non-admin named someone else's `userId`. | Ask an administrator. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`\n- `GET /user/password-policy/assignments`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Target (optional, `sk`), current and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","password":"…","newPassword":"…"}}}},"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"password not correct — The current password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"password not correct","path":"/profile/user/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You can only change your own password — A non-admin named someone else's `userId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You can only change your own password","path":"/profile/user/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/password/change":{"post":{"operationId":"UsersController_passwordChange","summary":"Change a password","description":"Any signed-in person may call it. **Below ConfigAdmin** you change your own password only: a `userId` other than your own `sk` is refused (403); omit it to mean yourself. Your current password is required unless the account is on a temporary password. The password policy that governs you (see *Password policies* below) must allow self-service change — except a temporary-password account finishing its forced first change, which is always allowed — and the new password must meet that policy's length and complexity rules. **ConfigAdmin and above** change anyone's password as before, without the policy switch. Success clears `temporaryPassword`.\n\n#### Signature\n\n```http\nPOST /user/password/change (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Password policies: a `passwordpolicy` record carries `allowSelfServiceChange` / `allowSelfServiceReset` (both default on) and the rules (`minLenght`, `minLowercase`, `minUppercase`, `minNumbers`, `minSymbols`, `exclude`, `pattern`). A user, group or role points at one through `data.passwordPolicy` (the policy name); a policy with `isDefault: true` covers everyone else. Precedence: user > group > role > org default; within a level the first by name. **No policy applies = full self-service, no rules.**\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSWORD | password not correct | The current password is wrong. | The change is refused; the existing password stands. |\n| `403` | NOT_YOURS | You can only change your own password | A non-admin named someone else's `userId`. | Ask an administrator. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`\n- `GET /user/password-policy/assignments`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Target (optional, `sk`), current and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","password":"…","newPassword":"…"}}}},"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"password not correct — The current password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"password not correct","path":"/user/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You can only change your own password — A non-admin named someone else's `userId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You can only change your own password","path":"/user/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/password/change":{"post":{"operationId":"UsersController_passwordChange","summary":"Change a password","description":"Any signed-in person may call it. **Below ConfigAdmin** you change your own password only: a `userId` other than your own `sk` is refused (403); omit it to mean yourself. Your current password is required unless the account is on a temporary password. The password policy that governs you (see *Password policies* below) must allow self-service change — except a temporary-password account finishing its forced first change, which is always allowed — and the new password must meet that policy's length and complexity rules. **ConfigAdmin and above** change anyone's password as before, without the policy switch. Success clears `temporaryPassword`.\n\n#### Signature\n\n```http\nPOST /user/user/password/change (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Password policies: a `passwordpolicy` record carries `allowSelfServiceChange` / `allowSelfServiceReset` (both default on) and the rules (`minLenght`, `minLowercase`, `minUppercase`, `minNumbers`, `minSymbols`, `exclude`, `pattern`). A user, group or role points at one through `data.passwordPolicy` (the policy name); a policy with `isDefault: true` covers everyone else. Precedence: user > group > role > org default; within a level the first by name. **No policy applies = full self-service, no rules.**\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSWORD | password not correct | The current password is wrong. | The change is refused; the existing password stands. |\n| `403` | NOT_YOURS | You can only change your own password | A non-admin named someone else's `userId`. | Ask an administrator. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`\n- `GET /user/password-policy/assignments`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Target (optional, `sk`), current and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","password":"…","newPassword":"…"}}}},"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"password not correct — The current password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"password not correct","path":"/user/user/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You can only change your own password — A non-admin named someone else's `userId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You can only change your own password","path":"/user/user/password/change","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/password-policy/assignments":{"get":{"operationId":"UsersController_passwordPolicyAssignments","summary":"Password policies and who they apply to","description":"Every password policy with the people, groups and roles that carry it (`data.passwordPolicy`), whether it is the org default, and its self-service switches — plus `options` (all users, groups, roles with their current policy) for assigning one. Precedence when several apply: user > group > role > org default.\n\n#### Signature\n\n```http\nGET /profile/user/password-policy/assignments () -> { precedence, policies[], options }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- ConfigAdmin.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/password-policy/assign`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ precedence, policies[], options }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/user/password-policy/assignments":{"get":{"operationId":"UsersController_passwordPolicyAssignments","summary":"Password policies and who they apply to","description":"Every password policy with the people, groups and roles that carry it (`data.passwordPolicy`), whether it is the org default, and its self-service switches — plus `options` (all users, groups, roles with their current policy) for assigning one. Precedence when several apply: user > group > role > org default.\n\n#### Signature\n\n```http\nGET /user/user/password-policy/assignments () -> { precedence, policies[], options }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- ConfigAdmin.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/password-policy/assign`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ precedence, policies[], options }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/user/password-policy/assign":{"post":{"operationId":"UsersController_assignPasswordPolicy","summary":"Assign a password policy","description":"Puts a policy on one user, group or role (sets its `data.passwordPolicy` to the policy name), or clears it with an empty `policy`. Returns the refreshed assignments.\n\n#### Signature\n\n```http\nPOST /profile/user/password-policy/assign (body) -> Same shape as the assignments listing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- ConfigAdmin. Root groups and roles can only be changed from the root organisation.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | No password policy named floor-staff | The policy or the target does not exist. | Check the name / sk. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /user/password-policy/assignments`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Same shape as the assignments listing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No password policy named floor-staff — The policy or the target does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No password policy named floor-staff","path":"/profile/user/password-policy/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"What to assign to, and which policy.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"kind":"group","target":"65f0c2a1e4b0a1b2c3d4e5f6","policy":"floor-staff"}}}}}},"/user/user/password-policy/assign":{"post":{"operationId":"UsersController_assignPasswordPolicy","summary":"Assign a password policy","description":"Puts a policy on one user, group or role (sets its `data.passwordPolicy` to the policy name), or clears it with an empty `policy`. Returns the refreshed assignments.\n\n#### Signature\n\n```http\nPOST /user/user/password-policy/assign (body) -> Same shape as the assignments listing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- ConfigAdmin. Root groups and roles can only be changed from the root organisation.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | No password policy named floor-staff | The policy or the target does not exist. | Check the name / sk. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /user/password-policy/assignments`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Same shape as the assignments listing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No password policy named floor-staff — The policy or the target does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No password policy named floor-staff","path":"/user/user/password-policy/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"What to assign to, and which policy.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"kind":"group","target":"65f0c2a1e4b0a1b2c3d4e5f6","policy":"floor-staff"}}}}}},"/profile/user/two-factor/status/{userType}/{userId}":{"get":{"operationId":"UsersController_twoFactorStatusForAccount","summary":"Read another account's second-factor state","description":"For an administrator looking at someone else's profile: whether 2FA is on, the method, when it was verified, how many backup codes remain, which methods the org offers, and the enrolled factors (`factors`, `defaultFactorId`, `twoFactorEnabled`). No secrets and no phone numbers beyond the factor label.\n\n#### Signature\n\n```http\nGET /profile/user/two-factor/status/{userType}/{userId} (userType: string, userId: string) -> Second-factor state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/two-factor/reset`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userType","required":true,"in":"path","schema":{"type":"string","enum":["user","customer"]},"description":"`customer` for a customer; anything else means a staff user.","example":"user"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The account `sk`.","example":"65f0c2a1e4b0a1b2c3d4e5f6"}],"responses":{"200":{"description":"Second-factor state","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"enabled":true,"method":"authenticator","backupCodesRemaining":7,"availableMethods":["authenticator","email"],"twoFactorEnabled":true,"defaultFactorId":"f1","factors":[{"id":"f1","type":"authenticator","label":"Authenticator app","status":"verified","isDefault":true}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"]}},"/user/user/two-factor/status/{userType}/{userId}":{"get":{"operationId":"UsersController_twoFactorStatusForAccount","summary":"Read another account's second-factor state","description":"For an administrator looking at someone else's profile: whether 2FA is on, the method, when it was verified, how many backup codes remain, which methods the org offers, and the enrolled factors (`factors`, `defaultFactorId`, `twoFactorEnabled`). No secrets and no phone numbers beyond the factor label.\n\n#### Signature\n\n```http\nGET /user/user/two-factor/status/{userType}/{userId} (userType: string, userId: string) -> Second-factor state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/two-factor/reset`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userType","required":true,"in":"path","schema":{"type":"string","enum":["user","customer"]},"description":"`customer` for a customer; anything else means a staff user.","example":"user"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The account `sk`.","example":"65f0c2a1e4b0a1b2c3d4e5f6"}],"responses":{"200":{"description":"Second-factor state","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"enabled":true,"method":"authenticator","backupCodesRemaining":7,"availableMethods":["authenticator","email"],"twoFactorEnabled":true,"defaultFactorId":"f1","factors":[{"id":"f1","type":"authenticator","label":"Authenticator app","status":"verified","isDefault":true}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"]}},"/profile/user/two-factor/reset":{"post":{"operationId":"UsersController_resetTwoFactor","summary":"Clear an account's second factor","description":"For an account locked out of its own 2FA — a lost authenticator with no backup codes left. Removes every enrolled factor and the backup codes and turns 2FA off; the owner enrols again from their own session. The reset is logged with the administrator and the reason.\n\n#### Signature\n\n```http\nPOST /profile/user/two-factor/reset (body) -> { userId, userType, twoFactorEnabled: false, reset: true }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NO_SECURITY_RECORD | No security record for that account | The account has never had security settings. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /user/two-factor/status/{userType}/{userId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ userId, userType, twoFactorEnabled: false, reset: true }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No security record for that account — The account has never had security settings.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No security record for that account","path":"/profile/user/two-factor/reset","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"requestBody":{"description":"Whose factor to clear.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string"},"userType":{"type":"string","enum":["user","customer"],"default":"user"},"reason":{"type":"string"}}},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","userType":"user","reason":"Lost phone, identity checked by phone call"}}}}}},"/user/user/two-factor/reset":{"post":{"operationId":"UsersController_resetTwoFactor","summary":"Clear an account's second factor","description":"For an account locked out of its own 2FA — a lost authenticator with no backup codes left. Removes every enrolled factor and the backup codes and turns 2FA off; the owner enrols again from their own session. The reset is logged with the administrator and the reason.\n\n#### Signature\n\n```http\nPOST /user/user/two-factor/reset (body) -> { userId, userType, twoFactorEnabled: false, reset: true }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NO_SECURITY_RECORD | No security record for that account | The account has never had security settings. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /user/two-factor/status/{userType}/{userId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ userId, userType, twoFactorEnabled: false, reset: true }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No security record for that account — The account has never had security settings.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No security record for that account","path":"/user/user/two-factor/reset","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"requestBody":{"description":"Whose factor to clear.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string"},"userType":{"type":"string","enum":["user","customer"],"default":"user"},"reason":{"type":"string"}}},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","userType":"user","reason":"Lost phone, identity checked by phone call"}}}}}},"/profile/user/profile/{emailOrUsername}":{"get":{"operationId":"UsersController_userProfile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"}],"responses":{"200":{"description":"The user profile","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Get a user profile (admins only)","description":"Fetches a user’s full profile by email or username. Admins only (ConfigAdmin) — anyone else gets 403. To look up a colleague (name, title, email, phone) use `POST /repository/find/user` with `{ \"query\": { \"data.email\": \"someone@company.com\" } }`.\n\n#### Signature\n\n```http\nGET /profile/user/profile/{emailOrUsername} (emailOrUsername: string) -> The user profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/find/{datatype}`\n- `POST /update`"}},"/user/user/profile/{emailOrUsername}":{"get":{"operationId":"UsersController_userProfile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"}],"responses":{"200":{"description":"The user profile","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Get a user profile (admins only)","description":"Fetches a user’s full profile by email or username. Admins only (ConfigAdmin) — anyone else gets 403. To look up a colleague (name, title, email, phone) use `POST /repository/find/user` with `{ \"query\": { \"data.email\": \"someone@company.com\" } }`.\n\n#### Signature\n\n```http\nGET /user/user/profile/{emailOrUsername} (emailOrUsername: string) -> The user profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /repository/find/{datatype}`\n- `POST /update`"}},"/profile/user/delete/{emailOrUsername}":{"delete":{"operationId":"UsersController_deleteUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"},{"name":"reason","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Delete a user","description":"Deletes a user account properly — notifying them, unwinding sessions and recording the removal. This is the endpoint the generic repository delete refuses in favour of.\n\n#### Signature\n\n```http\nDELETE /profile/user/delete/{emailOrUsername} (emailOrUsername: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Use this rather than deleting the record through the repository API.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/delete/{datatype}/{id}`"}},"/user/user/delete/{emailOrUsername}":{"delete":{"operationId":"UsersController_deleteUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"},{"name":"reason","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Delete a user","description":"Deletes a user account properly — notifying them, unwinding sessions and recording the removal. This is the endpoint the generic repository delete refuses in favour of.\n\n#### Signature\n\n```http\nDELETE /user/user/delete/{emailOrUsername} (emailOrUsername: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Use this rather than deleting the record through the repository API.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/delete/{datatype}/{id}`"}},"/profile/user/self":{"delete":{"operationId":"UsersController_deleteSelfUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Delete my account","description":"Deletes the caller's own account. Irreversible, and the caller is signed out as a consequence.\n\n#### Signature\n\n```http\nDELETE /profile/user/self () -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /user/delete/{emailOrUsername}`"}},"/user/user/self":{"delete":{"operationId":"UsersController_deleteSelfUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Delete my account","description":"Deletes the caller's own account. Irreversible, and the caller is signed out as a consequence.\n\n#### Signature\n\n```http\nDELETE /user/user/self () -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /user/delete/{emailOrUsername}`"}},"/profile/password/forgot/{email}":{"get":{"operationId":"UsersController_passwordForgot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"},{"name":"strategy","required":true,"in":"query","schema":{"type":"string"}},{"name":"password","required":true,"in":"query","schema":{"type":"string"}},{"name":"redirectUrl","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Request a password reset","description":"Sends a password reset link (or, with `strategy=temporary_password`, a temporary password) to a staff user. When the password policy governing that person has `allowSelfServiceReset: false`, nothing is sent, the refusal is logged, and the response is exactly the one an unknown address gets — the switch cannot be used to learn which addresses have accounts. HR issuing a new hire's temporary password does not go through this route and is not affected.\n\n#### Signature\n\n```http\nGET /profile/password/forgot/{email} (email: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit it — otherwise it doubles as an email-bombing tool.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`"}},"/profile/user/password/forgot/{email}":{"get":{"operationId":"UsersController_passwordForgot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"},{"name":"strategy","required":true,"in":"query","schema":{"type":"string"}},{"name":"password","required":true,"in":"query","schema":{"type":"string"}},{"name":"redirectUrl","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Request a password reset","description":"Sends a password reset link (or, with `strategy=temporary_password`, a temporary password) to a staff user. When the password policy governing that person has `allowSelfServiceReset: false`, nothing is sent, the refusal is logged, and the response is exactly the one an unknown address gets — the switch cannot be used to learn which addresses have accounts. HR issuing a new hire's temporary password does not go through this route and is not affected.\n\n#### Signature\n\n```http\nGET /profile/user/password/forgot/{email} (email: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit it — otherwise it doubles as an email-bombing tool.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`"}},"/user/password/forgot/{email}":{"get":{"operationId":"UsersController_passwordForgot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"},{"name":"strategy","required":true,"in":"query","schema":{"type":"string"}},{"name":"password","required":true,"in":"query","schema":{"type":"string"}},{"name":"redirectUrl","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Request a password reset","description":"Sends a password reset link (or, with `strategy=temporary_password`, a temporary password) to a staff user. When the password policy governing that person has `allowSelfServiceReset: false`, nothing is sent, the refusal is logged, and the response is exactly the one an unknown address gets — the switch cannot be used to learn which addresses have accounts. HR issuing a new hire's temporary password does not go through this route and is not affected.\n\n#### Signature\n\n```http\nGET /user/password/forgot/{email} (email: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit it — otherwise it doubles as an email-bombing tool.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`"}},"/user/user/password/forgot/{email}":{"get":{"operationId":"UsersController_passwordForgot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"},{"name":"strategy","required":true,"in":"query","schema":{"type":"string"}},{"name":"password","required":true,"in":"query","schema":{"type":"string"}},{"name":"redirectUrl","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Request a password reset","description":"Sends a password reset link (or, with `strategy=temporary_password`, a temporary password) to a staff user. When the password policy governing that person has `allowSelfServiceReset: false`, nothing is sent, the refusal is logged, and the response is exactly the one an unknown address gets — the switch cannot be used to learn which addresses have accounts. HR issuing a new hire's temporary password does not go through this route and is not affected.\n\n#### Signature\n\n```http\nGET /user/user/password/forgot/{email} (email: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit it — otherwise it doubles as an email-bombing tool.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`"}},"/profile/password/reset":{"post":{"operationId":"UsersController_passwordReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Reset a password","description":"Sets a new password using a reset token. The token should be single-use and short-lived; validate it first if you want to check before asking for the new password. For a staff user the governing password policy is checked again: with self-service reset switched off the token is refused as invalid (a link issued before the switch no longer works), and the new password must meet the policy's rules.\n\n#### Signature\n\n```http\nPOST /profile/password/reset (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/validate-token`","requestBody":{"description":"Token and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q","newPassword":"…"}}}}}},"/profile/user/password/reset":{"post":{"operationId":"UsersController_passwordReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Reset a password","description":"Sets a new password using a reset token. The token should be single-use and short-lived; validate it first if you want to check before asking for the new password. For a staff user the governing password policy is checked again: with self-service reset switched off the token is refused as invalid (a link issued before the switch no longer works), and the new password must meet the policy's rules.\n\n#### Signature\n\n```http\nPOST /profile/user/password/reset (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/validate-token`","requestBody":{"description":"Token and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q","newPassword":"…"}}}}}},"/user/password/reset":{"post":{"operationId":"UsersController_passwordReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Reset a password","description":"Sets a new password using a reset token. The token should be single-use and short-lived; validate it first if you want to check before asking for the new password. For a staff user the governing password policy is checked again: with self-service reset switched off the token is refused as invalid (a link issued before the switch no longer works), and the new password must meet the policy's rules.\n\n#### Signature\n\n```http\nPOST /user/password/reset (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/validate-token`","requestBody":{"description":"Token and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q","newPassword":"…"}}}}}},"/user/user/password/reset":{"post":{"operationId":"UsersController_passwordReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Reset a password","description":"Sets a new password using a reset token. The token should be single-use and short-lived; validate it first if you want to check before asking for the new password. For a staff user the governing password policy is checked again: with self-service reset switched off the token is refused as invalid (a link issued before the switch no longer works), and the new password must meet the policy's rules.\n\n#### Signature\n\n```http\nPOST /user/user/password/reset (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/validate-token`","requestBody":{"description":"Token and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q","newPassword":"…"}}}}}},"/profile/password/validate-token":{"post":{"operationId":"UsersController_validateResetToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the token is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Validate a reset token","description":"Checks a password reset token before showing the reset form, so an expired link fails early rather than after the user has typed a new password.\n\n#### Signature\n\n```http\nPOST /profile/password/validate-token (body) -> Whether the token is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`","requestBody":{"description":"The token to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q"}}}}}},"/profile/user/password/validate-token":{"post":{"operationId":"UsersController_validateResetToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the token is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Validate a reset token","description":"Checks a password reset token before showing the reset form, so an expired link fails early rather than after the user has typed a new password.\n\n#### Signature\n\n```http\nPOST /profile/user/password/validate-token (body) -> Whether the token is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`","requestBody":{"description":"The token to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q"}}}}}},"/user/password/validate-token":{"post":{"operationId":"UsersController_validateResetToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the token is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Validate a reset token","description":"Checks a password reset token before showing the reset form, so an expired link fails early rather than after the user has typed a new password.\n\n#### Signature\n\n```http\nPOST /user/password/validate-token (body) -> Whether the token is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`","requestBody":{"description":"The token to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q"}}}}}},"/user/user/password/validate-token":{"post":{"operationId":"UsersController_validateResetToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the token is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Validate a reset token","description":"Checks a password reset token before showing the reset form, so an expired link fails early rather than after the user has typed a new password.\n\n#### Signature\n\n```http\nPOST /user/user/password/validate-token (body) -> Whether the token is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /password/reset`","requestBody":{"description":"The token to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q"}}}}}},"/profile/user/invite/validate":{"post":{"operationId":"UsersController_inviteValidate","summary":"Validate an invitation","description":"Checks an invitation token before showing the acceptance form, so an expired or used invitation fails before the user fills anything in.\n\n#### Signature\n\n```http\nPOST /profile/user/invite/validate (body) -> Whether the invitation is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_TOKEN | Invalid or expired invitation token | The token is unrecognised or has expired. | Ask an admin to resend the invitation. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/complete`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the invitation is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid or expired invitation token — The token is unrecognised or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or expired invitation token","path":"/profile/user/invite/validate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"The token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"inv_9k2m4h1p7q"}}}}}},"/user/user/invite/validate":{"post":{"operationId":"UsersController_inviteValidate","summary":"Validate an invitation","description":"Checks an invitation token before showing the acceptance form, so an expired or used invitation fails before the user fills anything in.\n\n#### Signature\n\n```http\nPOST /user/user/invite/validate (body) -> Whether the invitation is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_TOKEN | Invalid or expired invitation token | The token is unrecognised or has expired. | Ask an admin to resend the invitation. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/complete`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the invitation is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid or expired invitation token — The token is unrecognised or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or expired invitation token","path":"/user/user/invite/validate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"The token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"inv_9k2m4h1p7q"}}}}}},"/profile/user/invite/complete":{"post":{"operationId":"UsersController_inviteComplete","summary":"Complete an invitation","description":"Accepts an invitation and creates the account. An invitation can only be completed once.\n\n#### Signature\n\n```http\nPOST /profile/user/invite/complete (body) -> The created account and tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ALREADY_COMPLETED | This invitation has already been completed | The invitation was used. | Sign in instead. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/validate`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created account and tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"This invitation has already been completed — The invitation was used.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This invitation has already been completed","path":"/profile/user/invite/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"The token and account details.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"inv_9k2m4h1p7q","password":"…","firstName":"Grace","lastName":"Hopper"}}}}}},"/user/user/invite/complete":{"post":{"operationId":"UsersController_inviteComplete","summary":"Complete an invitation","description":"Accepts an invitation and creates the account. An invitation can only be completed once.\n\n#### Signature\n\n```http\nPOST /user/user/invite/complete (body) -> The created account and tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ALREADY_COMPLETED | This invitation has already been completed | The invitation was used. | Sign in instead. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/validate`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created account and tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"This invitation has already been completed — The invitation was used.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This invitation has already been completed","path":"/user/user/invite/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"requestBody":{"description":"The token and account details.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"inv_9k2m4h1p7q","password":"…","firstName":"Grace","lastName":"Hopper"}}}}}},"/profile/user/invite/resend/{invitationId}":{"post":{"operationId":"UsersController_inviteResend","summary":"Resend an invitation","description":"Re-sends an outstanding invitation — the correct response when the first was not received, since a second `send` is refused.\n\n#### Signature\n\n```http\nPOST /profile/user/invite/resend/{invitationId} (invitationId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVITATION_NOT_FOUND | Invitation not found | No invitation has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/send`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"invitationId","required":true,"in":"path","description":"Invitation id.","schema":{"type":"string"},"example":"INV-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invitation not found — No invitation has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invitation not found","path":"/profile/user/invite/resend/{invitationId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"]}},"/user/user/invite/resend/{invitationId}":{"post":{"operationId":"UsersController_inviteResend","summary":"Resend an invitation","description":"Re-sends an outstanding invitation — the correct response when the first was not received, since a second `send` is refused.\n\n#### Signature\n\n```http\nPOST /user/user/invite/resend/{invitationId} (invitationId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVITATION_NOT_FOUND | Invitation not found | No invitation has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/send`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"invitationId","required":true,"in":"path","description":"Invitation id.","schema":{"type":"string"},"example":"INV-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invitation not found — No invitation has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invitation not found","path":"/user/user/invite/resend/{invitationId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"]}},"/profile/user/invite/send":{"post":{"operationId":"UsersController_inviteSend","summary":"Send an invitation","description":"Invites someone to join the organization. Only one active invitation per address at a time — a second is refused rather than sending a duplicate.\n\n#### Signature\n\n```http\nPOST /profile/user/invite/send (body) -> The invitation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVITE_EXISTS | An active invitation already exists for this email. Please wait for it to expire. | An unexpired invitation is already outstanding. | Resend the existing invitation rather than creating another. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/resend/{invitationId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The invitation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An active invitation already exists for this email. Please wait for it to expire. — An unexpired invitation is already outstanding.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An active invitation already exists for this email. Please wait for it to expire.","path":"/profile/user/invite/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"requestBody":{"description":"Who to invite.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"grace@example.com","roles":["User"],"message":"Welcome aboard"}}}}}},"/user/user/invite/send":{"post":{"operationId":"UsersController_inviteSend","summary":"Send an invitation","description":"Invites someone to join the organization. Only one active invitation per address at a time — a second is refused rather than sending a duplicate.\n\n#### Signature\n\n```http\nPOST /user/user/invite/send (body) -> The invitation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVITE_EXISTS | An active invitation already exists for this email. Please wait for it to expire. | An unexpired invitation is already outstanding. | Resend the existing invitation rather than creating another. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/invite/resend/{invitationId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The invitation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An active invitation already exists for this email. Please wait for it to expire. — An unexpired invitation is already outstanding.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An active invitation already exists for this email. Please wait for it to expire.","path":"/user/user/invite/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"requestBody":{"description":"Who to invite.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"grace@example.com","roles":["User"],"message":"Welcome aboard"}}}}}},"/profile/user/directory/lookup":{"post":{"operationId":"UsersController_directoryLookup","summary":"Find the orgs an email belongs to","parameters":[],"responses":{"201":{"description":"{ orgs }","content":{"application/json":{"schema":{"type":"object","properties":{"orgs":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"displayName":{"type":"string"}}}}}},"example":{"orgs":[{"orgId":"acme","displayName":"Acme"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"description":"Sign-in step one when the org is not known: the organizations this email has an account in, most recently used first, as `{ orgId, displayName }`. An unknown email answers `{ orgs: [] }`.\n\n#### Signature\n\n```http\nPOST /profile/user/directory/lookup (body) -> { orgs }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated, and it reveals which orgs an address belongs to — an enumeration surface.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`","requestBody":{"description":"The email.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}},"example":{"email":"ada@example.com"}}}}}},"/user/user/directory/lookup":{"post":{"operationId":"UsersController_directoryLookup","summary":"Find the orgs an email belongs to","parameters":[],"responses":{"201":{"description":"{ orgs }","content":{"application/json":{"schema":{"type":"object","properties":{"orgs":{"type":"array","items":{"type":"object","properties":{"orgId":{"type":"string"},"displayName":{"type":"string"}}}}}},"example":{"orgs":[{"orgId":"acme","displayName":"Acme"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"description":"Sign-in step one when the org is not known: the organizations this email has an account in, most recently used first, as `{ orgId, displayName }`. An unknown email answers `{ orgs: [] }`.\n\n#### Signature\n\n```http\nPOST /user/user/directory/lookup (body) -> { orgs }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated, and it reveals which orgs an address belongs to — an enumeration surface.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`","requestBody":{"description":"The email.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}},"example":{"email":"ada@example.com"}}}}}},"/profile/user/directory/backfill":{"post":{"operationId":"UsersController_directoryBackfill","summary":"Rebuild the account directory","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"{ orgs, users }","content":{"application/json":{"schema":{"type":"object","properties":{"orgs":{"type":"integer"},"users":{"type":"integer"}}},"example":{"orgs":42,"users":1310}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Indexes every user of every org into the account directory that sign-in without an `orgid` uses. A one-off after the directory was introduced; sign-ins keep it current afterwards. Returns how many orgs and users were indexed.\n\n#### Signature\n\n```http\nPOST /profile/user/directory/backfill () -> { orgs, users }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Platform-wide: it reads every org, not just the caller's.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/directory/lookup`"}},"/user/user/directory/backfill":{"post":{"operationId":"UsersController_directoryBackfill","summary":"Rebuild the account directory","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"{ orgs, users }","content":{"application/json":{"schema":{"type":"object","properties":{"orgs":{"type":"integer"},"users":{"type":"integer"}}},"example":{"orgs":42,"users":1310}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"description":"Indexes every user of every org into the account directory that sign-in without an `orgid` uses. A one-off after the directory was introduced; sign-ins keep it current afterwards. Returns how many orgs and users were indexed.\n\n#### Signature\n\n```http\nPOST /user/user/directory/backfill () -> { orgs, users }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Platform-wide: it reads every org, not just the caller's.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/directory/lookup`"}},"/profile/session-access":{"post":{"operationId":"UsersController_sessionAccessToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The scoped token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Get a session access token","description":"Issues a scoped access token for the current session — used where a component needs a narrower token than the caller holds.\n\n#### Signature\n\n```http\nPOST /profile/session-access (body) -> The scoped token\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/refresh`","requestBody":{"description":"What the token is for.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"scope":"storefront"}}}}}},"/user/session-access":{"post":{"operationId":"UsersController_sessionAccessToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The scoped token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Get a session access token","description":"Issues a scoped access token for the current session — used where a component needs a narrower token than the caller holds.\n\n#### Signature\n\n```http\nPOST /user/session-access (body) -> The scoped token\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /user/refresh`","requestBody":{"description":"What the token is for.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"scope":"storefront"}}}}}},"/profile/update":{"post":{"operationId":"UsersController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Update a user","description":"Updates a user record: the body is merged over the stored record, so roles and groups sent here are written too. Admin only, and root roles or root groups in `data.roles` / `data.groups` are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/update (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/add`","requestBody":{"description":"The user to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","firstName":"Ada","lastName":"Lovelace"}}}}}},"/profile/user/update":{"post":{"operationId":"UsersController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/user/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Update a user","description":"Updates a user record: the body is merged over the stored record, so roles and groups sent here are written too. Admin only, and root roles or root groups in `data.roles` / `data.groups` are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/user/update (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/add`","requestBody":{"description":"The user to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","firstName":"Ada","lastName":"Lovelace"}}}}}},"/user/update":{"post":{"operationId":"UsersController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Update a user","description":"Updates a user record: the body is merged over the stored record, so roles and groups sent here are written too. Admin only, and root roles or root groups in `data.roles` / `data.groups` are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/update (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/add`","requestBody":{"description":"The user to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","firstName":"Ada","lastName":"Lovelace"}}}}}},"/user/user/update":{"post":{"operationId":"UsersController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/user/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Profiles"],"summary":"Update a user","description":"Updates a user record: the body is merged over the stored record, so roles and groups sent here are written too. Admin only, and root roles or root groups in `data.roles` / `data.groups` are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/user/update (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/add`","requestBody":{"description":"The user to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","firstName":"Ada","lastName":"Lovelace"}}}}}},"/profile/user/group/add":{"post":{"operationId":"UsersController_groupAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/profile/user/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/user/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Add a user to a group","description":"Adds a staff user (found by `email`, `username` or `id`) to one or more groups, by group name or `sk`. Group membership carries the group's roles and permissions, so this is a privilege change — admin only. A group that grants a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/user/group/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/remove`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["groups"],"properties":{"email":{"type":"string"},"username":{"type":"string"},"id":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/profile/group/add":{"post":{"operationId":"UsersController_groupAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/profile/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Add a user to a group","description":"Adds a staff user (found by `email`, `username` or `id`) to one or more groups, by group name or `sk`. Group membership carries the group's roles and permissions, so this is a privilege change — admin only. A group that grants a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/group/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/remove`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["groups"],"properties":{"email":{"type":"string"},"username":{"type":"string"},"id":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/user/user/group/add":{"post":{"operationId":"UsersController_groupAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/user/user/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/user/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Add a user to a group","description":"Adds a staff user (found by `email`, `username` or `id`) to one or more groups, by group name or `sk`. Group membership carries the group's roles and permissions, so this is a privilege change — admin only. A group that grants a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/user/group/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/remove`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["groups"],"properties":{"email":{"type":"string"},"username":{"type":"string"},"id":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/user/group/add":{"post":{"operationId":"UsersController_groupAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/user/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/group/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Add a user to a group","description":"Adds a staff user (found by `email`, `username` or `id`) to one or more groups, by group name or `sk`. Group membership carries the group's roles and permissions, so this is a privilege change — admin only. A group that grants a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/group/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/remove`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["groups"],"properties":{"email":{"type":"string"},"username":{"type":"string"},"id":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/profile/user/group/remove":{"post":{"operationId":"UsersController_groupRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/profile/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"user not found — No staff user with that email.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"user not found","path":"/profile/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Remove a user from a group","description":"Removes a staff user (by `email`) from one or more groups, withdrawing the access they conferred. Taking someone out of a root group is refused outside the root org, same as putting them in.\n\n#### Signature\n\n```http\nPOST /profile/user/group/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `404` | USER_NOT_FOUND | user not found | No staff user with that email. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/add`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","groups"],"properties":{"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/profile/group/remove":{"post":{"operationId":"UsersController_groupRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/profile/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"user not found — No staff user with that email.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"user not found","path":"/profile/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Remove a user from a group","description":"Removes a staff user (by `email`) from one or more groups, withdrawing the access they conferred. Taking someone out of a root group is refused outside the root org, same as putting them in.\n\n#### Signature\n\n```http\nPOST /profile/group/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `404` | USER_NOT_FOUND | user not found | No staff user with that email. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/add`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","groups"],"properties":{"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/user/user/group/remove":{"post":{"operationId":"UsersController_groupRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/user/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"user not found — No staff user with that email.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"user not found","path":"/user/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Remove a user from a group","description":"Removes a staff user (by `email`) from one or more groups, withdrawing the access they conferred. Taking someone out of a root group is refused outside the root org, same as putting them in.\n\n#### Signature\n\n```http\nPOST /user/user/group/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `404` | USER_NOT_FOUND | user not found | No staff user with that email. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/add`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","groups"],"properties":{"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/user/group/remove":{"post":{"operationId":"UsersController_groupRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Groups are required — `groups` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Groups are required","path":"/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"user not found — No staff user with that email.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"user not found","path":"/user/group/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Remove a user from a group","description":"Removes a staff user (by `email`) from one or more groups, withdrawing the access they conferred. Taking someone out of a root group is refused outside the root org, same as putting them in.\n\n#### Signature\n\n```http\nPOST /user/group/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_REQUIRED | Groups are required | `groups` is empty. | — |\n| `404` | USER_NOT_FOUND | user not found | No staff user with that email. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /group/add`","requestBody":{"description":"The user and groups.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","groups"],"properties":{"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","groups":["engineering"]}}}}}},"/profile/user/lockout":{"post":{"operationId":"UsersController_setUserLockout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The user record","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"User ID and a boolean locked value are required — `userId` is empty or `locked` is not a boolean.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User ID and a boolean locked value are required","path":"/profile/user/lockout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an organization owner can change account access — The caller is not an Owner of this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an organization owner can change account access","path":"/profile/user/lockout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"User not found — No staff user with that id in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"User not found","path":"/profile/user/lockout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Switch a user's access off or on","description":"An org owner locks (or unlocks) another staff user of the same org. A locked user is refused at sign-in and on every session check with \"Account is locked. Contact your organization owner.\"; locking also pushes a realtime sign-out to their open sessions. Owners and system accounts cannot be locked, and nobody can lock themselves. Returns the refreshed user record.\n\n#### Signature\n\n```http\nPOST /profile/user/lockout (body) -> The user record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | LOCKOUT_INVALID | User ID and a boolean locked value are required | `userId` is empty or `locked` is not a boolean. | — |\n| `403` | NOT_OWNER | Only an organization owner can change account access | The caller is not an Owner of this org. | — |\n| `404` | USER_NOT_FOUND | User not found | No staff user with that id in this org. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"description":"Who, and which way.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","locked"],"properties":{"userId":{"type":"string","description":"User `sk`."},"locked":{"type":"boolean"}}},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","locked":true}}}}}},"/user/user/lockout":{"post":{"operationId":"UsersController_setUserLockout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The user record","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"User ID and a boolean locked value are required — `userId` is empty or `locked` is not a boolean.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User ID and a boolean locked value are required","path":"/user/user/lockout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an organization owner can change account access — The caller is not an Owner of this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an organization owner can change account access","path":"/user/user/lockout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"User not found — No staff user with that id in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"User not found","path":"/user/user/lockout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Switch a user's access off or on","description":"An org owner locks (or unlocks) another staff user of the same org. A locked user is refused at sign-in and on every session check with \"Account is locked. Contact your organization owner.\"; locking also pushes a realtime sign-out to their open sessions. Owners and system accounts cannot be locked, and nobody can lock themselves. Returns the refreshed user record.\n\n#### Signature\n\n```http\nPOST /user/user/lockout (body) -> The user record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | LOCKOUT_INVALID | User ID and a boolean locked value are required | `userId` is empty or `locked` is not a boolean. | — |\n| `403` | NOT_OWNER | Only an organization owner can change account access | The caller is not an Owner of this org. | — |\n| `404` | USER_NOT_FOUND | User not found | No staff user with that id in this org. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"description":"Who, and which way.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","locked"],"properties":{"userId":{"type":"string","description":"User `sk`."},"locked":{"type":"boolean"}}},"example":{"userId":"65f0c2a1e4b0a1b2c3d4e5f6","locked":true}}}}}},"/profile/user/role/add":{"post":{"operationId":"UsersController_roleAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"User and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User and roles are required","path":"/profile/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"role not found ContentReviewer — A custom role name does not exist in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"role not found ContentReviewer","path":"/profile/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Grant a role","description":"Grants one or more roles to a staff user (`email` may also be the user `sk`). Built-in roles are used as named; a custom role is resolved in this org by name or `sk` and stored by its name. **This is a direct privilege escalation** — admin only, and root roles are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/user/role/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | User and roles are required | `email` or `roles` is empty. | — |\n| `404` | ROLE_NOT_FOUND | role not found ContentReviewer | A custom role name does not exist in this org. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/remove`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/profile/role/add":{"post":{"operationId":"UsersController_roleAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"User and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User and roles are required","path":"/profile/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"role not found ContentReviewer — A custom role name does not exist in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"role not found ContentReviewer","path":"/profile/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Grant a role","description":"Grants one or more roles to a staff user (`email` may also be the user `sk`). Built-in roles are used as named; a custom role is resolved in this org by name or `sk` and stored by its name. **This is a direct privilege escalation** — admin only, and root roles are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/role/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | User and roles are required | `email` or `roles` is empty. | — |\n| `404` | ROLE_NOT_FOUND | role not found ContentReviewer | A custom role name does not exist in this org. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/remove`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/user/user/role/add":{"post":{"operationId":"UsersController_roleAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"User and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User and roles are required","path":"/user/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"role not found ContentReviewer — A custom role name does not exist in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"role not found ContentReviewer","path":"/user/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Grant a role","description":"Grants one or more roles to a staff user (`email` may also be the user `sk`). Built-in roles are used as named; a custom role is resolved in this org by name or `sk` and stored by its name. **This is a direct privilege escalation** — admin only, and root roles are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/user/role/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | User and roles are required | `email` or `roles` is empty. | — |\n| `404` | ROLE_NOT_FOUND | role not found ContentReviewer | A custom role name does not exist in this org. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/remove`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/user/role/add":{"post":{"operationId":"UsersController_roleAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"User and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"User and roles are required","path":"/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"role not found ContentReviewer — A custom role name does not exist in this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"role not found ContentReviewer","path":"/user/role/add","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Grant a role","description":"Grants one or more roles to a staff user (`email` may also be the user `sk`). Built-in roles are used as named; a custom role is resolved in this org by name or `sk` and stored by its name. **This is a direct privilege escalation** — admin only, and root roles are refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/role/add (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | User and roles are required | `email` or `roles` is empty. | — |\n| `404` | ROLE_NOT_FOUND | role not found ContentReviewer | A custom role name does not exist in this org. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /role/remove`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/profile/user/role/remove":{"post":{"operationId":"UsersController_roleRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Email and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email and roles are required","path":"/profile/user/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/user/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Revoke a role","description":"Removes one or more roles from a staff user, withdrawing the access they granted. Takes effect on their next token — an existing access token may retain the role until it expires. Revoking a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/user/role/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Revocation is not instant if the user holds a valid token — force a sign-out where it matters.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | Email and roles are required | `email` or `roles` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `GET /logout/{userId}`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/profile/role/remove":{"post":{"operationId":"UsersController_roleRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Email and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email and roles are required","path":"/profile/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/profile/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Revoke a role","description":"Removes one or more roles from a staff user, withdrawing the access they granted. Takes effect on their next token — an existing access token may retain the role until it expires. Revoking a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /profile/role/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Revocation is not instant if the user holds a valid token — force a sign-out where it matters.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | Email and roles are required | `email` or `roles` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `GET /logout/{userId}`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/user/user/role/remove":{"post":{"operationId":"UsersController_roleRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Email and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email and roles are required","path":"/user/user/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/user/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Revoke a role","description":"Removes one or more roles from a staff user, withdrawing the access they granted. Takes effect on their next token — an existing access token may retain the role until it expires. Revoking a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/user/role/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Revocation is not instant if the user holds a valid token — force a sign-out where it matters.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | Email and roles are required | `email` or `roles` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `GET /logout/{userId}`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/user/role/remove":{"post":{"operationId":"UsersController_roleRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated user","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"400":{"description":"Email and roles are required — `email` or `roles` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email and roles are required","path":"/user/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the root organization can give a user a root role. Blocked: RootAdmin. — A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the root organization can give a user a root role. Blocked: RootAdmin.","path":"/user/role/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Revoke a role","description":"Removes one or more roles from a staff user, withdrawing the access they granted. Takes effect on their next token — an existing access token may retain the role until it expires. Revoking a root role is refused outside the root org.\n\n#### Signature\n\n```http\nPOST /user/role/remove (body) -> The updated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `ConfigAdmin`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Revocation is not instant if the user holds a valid token — force a sign-out where it matters.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ROLES_REQUIRED | Email and roles are required | `email` or `roles` is empty. | — |\n| `403` | ROOT_ORG_REQUIRED | Only the root organization can give a user a root role. Blocked: RootAdmin. | A root role or root group is named (or a custom role/group resolves to one) and the caller's org is not the root org. The sentence names the act and the blocked names; the body also carries `code: \"ROOT_ORG_REQUIRED\"` and `blocked`. | Root access is managed from the root org only. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `GET /logout/{userId}`","requestBody":{"description":"The user and roles.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","roles"],"properties":{"email":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}}},"example":{"email":"ada@example.com","roles":["ContentAdmin"]}}}}}},"/profile/app/register":{"post":{"operationId":"UsersController_appRegister","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"reset","required":false,"in":"query","schema":{"type":"string"},"description":"Rotate the existing key.","example":"true"},{"name":"id","required":false,"in":"query","schema":{"type":"string"},"description":"Existing app id to update.","example":"APP-4821"}],"responses":{"201":{"description":"The registered application and its key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app config <appId> not found for org <orgId> — The app id does not resolve for the organization.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"app config <appId> not found for org <orgId>","path":"/profile/app/register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Register an application","description":"Registers a client application and issues its credentials. Pass `reset` to rotate an existing app's key.\n\n#### Signature\n\n```http\nPOST /profile/app/register (reset?: string, id?: string, body) -> The registered application and its key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Rotating a key invalidates the old one — deploy the new key before rotating.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APP_CONFIG_NOT_FOUND | app config <appId> not found for org <orgId> | The app id does not resolve for the organization. | The message names both the app and the org it looked in. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /app/key`","requestBody":{"description":"The application to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Storefront web","appId":"storefront-web"}}}}}},"/user/app/register":{"post":{"operationId":"UsersController_appRegister","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"reset","required":false,"in":"query","schema":{"type":"string"},"description":"Rotate the existing key.","example":"true"},{"name":"id","required":false,"in":"query","schema":{"type":"string"},"description":"Existing app id to update.","example":"APP-4821"}],"responses":{"201":{"description":"The registered application and its key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app config <appId> not found for org <orgId> — The app id does not resolve for the organization.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"app config <appId> not found for org <orgId>","path":"/user/app/register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Register an application","description":"Registers a client application and issues its credentials. Pass `reset` to rotate an existing app's key.\n\n#### Signature\n\n```http\nPOST /user/app/register (reset?: string, id?: string, body) -> The registered application and its key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Rotating a key invalidates the old one — deploy the new key before rotating.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APP_CONFIG_NOT_FOUND | app config <appId> not found for org <orgId> | The app id does not resolve for the organization. | The message names both the app and the org it looked in. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /app/key`","requestBody":{"description":"The application to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Storefront web","appId":"storefront-web"}}}}}},"/profile/app/key":{"post":{"operationId":"UsersController_appKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The application key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Get an application key","description":"Retrieves an application's key. The key authenticates the application, so handle the response as a secret.\n\n#### Signature\n\n```http\nPOST /profile/app/key (body) -> The application key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /validate-app-key`","requestBody":{"description":"Which application.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"appId":"storefront-web"}}}}}},"/user/app/key":{"post":{"operationId":"UsersController_appKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The application key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Get an application key","description":"Retrieves an application's key. The key authenticates the application, so handle the response as a secret.\n\n#### Signature\n\n```http\nPOST /user/app/key (body) -> The application key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /validate-app-key`","requestBody":{"description":"Which application.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"appId":"storefront-web"}}}}}},"/profile/validate-app-key":{"post":{"operationId":"UsersController_validateAppKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the key is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Validate an application key","description":"Checks whether an application key is valid and active — what a client calls to confirm its credentials before relying on them.\n\n#### Signature\n\n```http\nPOST /profile/validate-app-key (body) -> Whether the key is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /app/key`","requestBody":{"description":"The key to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"appId":"storefront-web","key":"ak_9k2m4h1p7q"}}}}}},"/user/validate-app-key":{"post":{"operationId":"UsersController_validateAppKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the key is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Validate an application key","description":"Checks whether an application key is valid and active — what a client calls to confirm its credentials before relying on them.\n\n#### Signature\n\n```http\nPOST /user/validate-app-key (body) -> Whether the key is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /app/key`","requestBody":{"description":"The key to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"appId":"storefront-web","key":"ak_9k2m4h1p7q"}}}}}},"/profile/customer/signin":{"post":{"operationId":"UsersController_customerSignin","summary":"Customer sign in","description":"Authenticates a customer. Customers are a separate population from users — a customer account does not grant access to the back office.\n\n#### Signature\n\n```http\nPOST /profile/customer/signin (body) -> Tokens and the customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signup`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"…"}}}},"responses":{"201":{"description":"Tokens and the customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/profile/customer/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/profile/customer/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"]}},"/user/customer/signin":{"post":{"operationId":"UsersController_customerSignin","summary":"Customer sign in","description":"Authenticates a customer. Customers are a separate population from users — a customer account does not grant access to the back office.\n\n#### Signature\n\n```http\nPOST /user/customer/signin (body) -> Tokens and the customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n| `403` | ACCOUNT_LOCKED | Account is locked. Please contact support. | The account has been locked, typically after repeated failed attempts. | An operator must unlock it. Retrying does not help. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signup`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"…"}}}},"responses":{"201":{"description":"Tokens and the customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/user/customer/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Account is locked. Please contact support. — The account has been locked, typically after repeated failed attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Account is locked. Please contact support.","path":"/user/customer/signin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"]}},"/profile/customer/shared-account":{"post":{"operationId":"UsersController_setSharedAccount","summary":"Choose the account a customer buys for","description":"Switches a signed-in customer between buying as themselves and buying for a shared account they are associated with (`data.sharedAccounts`). Returns the customer with a re-issued token pair carrying the choice — store them in place of the current ones. Send no `accountId` to go back to buying as themselves.\n\n#### Signature\n\n```http\nPOST /profile/customer/shared-account (body) -> { customer, token, refreshToken, orgId }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_SIGNED_IN | Not signed in | No customer session. | — |\n| `403` | NOT_ASSOCIATED | You are not associated with this account | `accountId` is not one of the customer's shared accounts. | — |\n| `404` | CUSTOMER_NOT_FOUND | Customer not found | The customer record is gone. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ customer, token, refreshToken, orgId }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not signed in — No customer session.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not signed in","path":"/profile/customer/shared-account","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You are not associated with this account — `accountId` is not one of the customer's shared accounts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You are not associated with this account","path":"/profile/customer/shared-account","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Customer not found — The customer record is gone.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Customer not found","path":"/profile/customer/shared-account","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"requestBody":{"description":"The shared account, or nothing.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"accountId":{"type":"string"}}},"example":{"accountId":"acct_9k2m4h"}}}}}},"/user/customer/shared-account":{"post":{"operationId":"UsersController_setSharedAccount","summary":"Choose the account a customer buys for","description":"Switches a signed-in customer between buying as themselves and buying for a shared account they are associated with (`data.sharedAccounts`). Returns the customer with a re-issued token pair carrying the choice — store them in place of the current ones. Send no `accountId` to go back to buying as themselves.\n\n#### Signature\n\n```http\nPOST /user/customer/shared-account (body) -> { customer, token, refreshToken, orgId }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_SIGNED_IN | Not signed in | No customer session. | — |\n| `403` | NOT_ASSOCIATED | You are not associated with this account | `accountId` is not one of the customer's shared accounts. | — |\n| `404` | CUSTOMER_NOT_FOUND | Customer not found | The customer record is gone. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ customer, token, refreshToken, orgId }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not signed in — No customer session.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not signed in","path":"/user/customer/shared-account","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You are not associated with this account — `accountId` is not one of the customer's shared accounts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You are not associated with this account","path":"/user/customer/shared-account","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Customer not found — The customer record is gone.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Customer not found","path":"/user/customer/shared-account","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"requestBody":{"description":"The shared account, or nothing.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"accountId":{"type":"string"}}},"example":{"accountId":"acct_9k2m4h"}}}}}},"/profile/customer/dashboard/auth/{authOrgId}":{"post":{"operationId":"UsersController_customerDashboardAuth","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"authOrgId","required":true,"in":"path","schema":{"type":"string"},"description":"Organization to authenticate against.","example":"acme-retail"}],"responses":{"201":{"description":"Tokens for that organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Authenticate a customer for a dashboard","description":"Authenticates a customer against another organization's dashboard — the cross-org hop for a shared portal.\n\n#### Signature\n\n```http\nPOST /profile/customer/dashboard/auth/{authOrgId} (authOrgId: string, body) -> Tokens for that organization\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /customer/signin`","requestBody":{"description":"Auth context.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/user/customer/dashboard/auth/{authOrgId}":{"post":{"operationId":"UsersController_customerDashboardAuth","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"authOrgId","required":true,"in":"path","schema":{"type":"string"},"description":"Organization to authenticate against.","example":"acme-retail"}],"responses":{"201":{"description":"Tokens for that organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Authenticate a customer for a dashboard","description":"Authenticates a customer against another organization's dashboard — the cross-org hop for a shared portal.\n\n#### Signature\n\n```http\nPOST /user/customer/dashboard/auth/{authOrgId} (authOrgId: string, body) -> Tokens for that organization\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /customer/signin`","requestBody":{"description":"Auth context.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/profile/user/shared-site-auth/{token}":{"get":{"operationId":"UsersController_userSharedSiteAuth","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"token","required":true,"in":"path","schema":{"type":"string"},"description":"Shared-site auth token.","example":"sst_9k2m4h1p7q"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Authenticate via a shared-site token","description":"Signs a user in from a token issued by another site in the same estate — the cross-site single sign-on hop. The token is a bearer credential in a URL, so it should be short-lived and single-use.\n\n#### Signature\n\n```http\nGET /profile/user/shared-site-auth/{token} (token: string) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Credentials in URLs leak through logs and referrers — keep the lifetime short.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`"}},"/user/user/shared-site-auth/{token}":{"get":{"operationId":"UsersController_userSharedSiteAuth","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"token","required":true,"in":"path","schema":{"type":"string"},"description":"Shared-site auth token.","example":"sst_9k2m4h1p7q"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Authenticate via a shared-site token","description":"Signs a user in from a token issued by another site in the same estate — the cross-site single sign-on hop. The token is a bearer credential in a URL, so it should be short-lived and single-use.\n\n#### Signature\n\n```http\nGET /user/user/shared-site-auth/{token} (token: string) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Credentials in URLs leak through logs and referrers — keep the lifetime short.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /signin`"}},"/profile/global-login":{"post":{"operationId":"UsersController_globalLogin","summary":"Global login","description":"Authenticates across organizations rather than within one — for a user who belongs to several and has not yet chosen.\n\n#### Signature\n\n```http\nPOST /profile/global-login (body) -> Tokens and the organizations available\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /system-orgs`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"…"}}}},"responses":{"201":{"description":"Tokens and the organizations available","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/profile/global-login","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/global-login":{"post":{"operationId":"UsersController_globalLogin","summary":"Global login","description":"Authenticates across organizations rather than within one — for a user who belongs to several and has not yet chosen.\n\n#### Signature\n\n```http\nPOST /user/global-login (body) -> Tokens and the organizations available\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CREDENTIALS | Invalid username or password | The identifier or password is wrong. | Deliberately does not distinguish an unknown account from a wrong password — do not surface a more specific message to the caller. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /system-orgs`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Credentials.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"…"}}}},"responses":{"201":{"description":"Tokens and the organizations available","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"Invalid username or password — The identifier or password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid username or password","path":"/user/global-login","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/customer/signup":{"post":{"operationId":"UsersController_customerSignup","summary":"Customer sign up","description":"Registers a customer account.\n\n#### Signature\n\n```http\nPOST /profile/customer/signup (body) -> The created customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CUSTOMER_EXISTS | A customer with this email already exists in the organization | The email is already registered as a customer. | Sign in instead. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signin`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The account to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"…","firstName":"Ada"}}}},"responses":{"201":{"description":"The created customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"A customer with this email already exists in the organization — The email is already registered as a customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A customer with this email already exists in the organization","path":"/profile/customer/signup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"]}},"/user/customer/signup":{"post":{"operationId":"UsersController_customerSignup","summary":"Customer sign up","description":"Registers a customer account.\n\n#### Signature\n\n```http\nPOST /user/customer/signup (body) -> The created customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CUSTOMER_EXISTS | A customer with this email already exists in the organization | The email is already registered as a customer. | Sign in instead. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signin`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The account to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","password":"…","firstName":"Ada"}}}},"responses":{"201":{"description":"The created customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"400":{"description":"A customer with this email already exists in the organization — The email is already registered as a customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A customer with this email already exists in the organization","path":"/user/customer/signup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"]}},"/profile/customer/refresh":{"post":{"operationId":"UsersController_customerRefreshToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"New tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Refresh a customer token","description":"Exchanges a customer refresh token for a new access token.\n\n#### Signature\n\n```http\nPOST /profile/customer/refresh (body) -> New tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signin`","requestBody":{"description":"The refresh token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"refreshToken":"eyJhbGciOi…"}}}}}},"/user/customer/refresh":{"post":{"operationId":"UsersController_customerRefreshToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"New tokens","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Refresh a customer token","description":"Exchanges a customer refresh token for a new access token.\n\n#### Signature\n\n```http\nPOST /user/customer/refresh (body) -> New tokens\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signin`","requestBody":{"description":"The refresh token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"refreshToken":"eyJhbGciOi…"}}}}}},"/profile/customer/subscribe":{"post":{"operationId":"UsersController_customerSubscribe","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Subscribe a customer","description":"Records a customer opting in to marketing. Capture what they consented to — see the promotions endpoints, which store the consent text.\n\n#### Signature\n\n```http\nPOST /profile/customer/subscribe (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/promotions/{name}/subscribe`","requestBody":{"description":"The subscription.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}}}},"/user/customer/subscribe":{"post":{"operationId":"UsersController_customerSubscribe","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Subscribe a customer","description":"Records a customer opting in to marketing. Capture what they consented to — see the promotions endpoints, which store the consent text.\n\n#### Signature\n\n```http\nPOST /user/customer/subscribe (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/promotions/{name}/subscribe`","requestBody":{"description":"The subscription.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}}}},"/profile/customer/unsubscribe":{"post":{"operationId":"UsersController_customerUnsubscribe","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Unsubscribe a customer","description":"Records a customer opting out. Honour it promptly and completely.\n\n#### Signature\n\n```http\nPOST /profile/customer/unsubscribe (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/subscribe`","requestBody":{"description":"The unsubscribe request.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}}}},"/user/customer/unsubscribe":{"post":{"operationId":"UsersController_customerUnsubscribe","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Unsubscribe a customer","description":"Records a customer opting out. Honour it promptly and completely.\n\n#### Signature\n\n```http\nPOST /user/customer/unsubscribe (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/subscribe`","requestBody":{"description":"The unsubscribe request.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}}}},"/profile/customer/update":{"post":{"operationId":"UsersController_customerUpdate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated customer","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Update a customer","description":"Updates a customer record.\n\n#### Signature\n\n```http\nPOST /profile/customer/update (body) -> The updated customer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /customer/profile/{emailOrUsername}`","requestBody":{"description":"The customer to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","firstName":"Ada"}}}}}},"/user/customer/update":{"post":{"operationId":"UsersController_customerUpdate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated customer","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Update a customer","description":"Updates a customer record.\n\n#### Signature\n\n```http\nPOST /user/customer/update (body) -> The updated customer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /customer/profile/{emailOrUsername}`","requestBody":{"description":"The customer to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","firstName":"Ada"}}}}}},"/profile/customer/password/change":{"post":{"operationId":"UsersController_customerPasswordChange","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Change a customer password","description":"Changes a customer's password, requiring the current one.\n\n#### Signature\n\n```http\nPOST /profile/customer/password/change (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/reset`","requestBody":{"description":"Current and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"currentPassword":"…","newPassword":"…"}}}}}},"/user/customer/password/change":{"post":{"operationId":"UsersController_customerPasswordChange","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Change a customer password","description":"Changes a customer's password, requiring the current one.\n\n#### Signature\n\n```http\nPOST /user/customer/password/change (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/reset`","requestBody":{"description":"Current and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"currentPassword":"…","newPassword":"…"}}}}}},"/profile/customer/password/reset":{"post":{"operationId":"UsersController_customerPasswordReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Reset a customer password","description":"Sets a new customer password using a reset token.\n\n#### Signature\n\n```http\nPOST /profile/customer/password/reset (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/validate-token`","requestBody":{"description":"Token and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q","newPassword":"…"}}}}}},"/user/customer/password/reset":{"post":{"operationId":"UsersController_customerPasswordReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"Client context recorded against the attempt — used for device tracking and blocking.","schema":{"type":"string"},"example":"web/1.4.2"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Reset a customer password","description":"Sets a new customer password using a reset token.\n\n#### Signature\n\n```http\nPOST /user/customer/password/reset (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/validate-token`","requestBody":{"description":"Token and new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q","newPassword":"…"}}}}}},"/profile/customer/password/validate-token":{"post":{"operationId":"UsersController_customerValidateResetToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the token is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Validate a customer reset token","description":"Checks a customer reset token before showing the reset form.\n\n#### Signature\n\n```http\nPOST /profile/customer/password/validate-token (body) -> Whether the token is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/reset`","requestBody":{"description":"The token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q"}}}}}},"/user/customer/password/validate-token":{"post":{"operationId":"UsersController_customerValidateResetToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the token is valid","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Validate a customer reset token","description":"Checks a customer reset token before showing the reset form.\n\n#### Signature\n\n```http\nPOST /user/customer/password/validate-token (body) -> Whether the token is valid\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/reset`","requestBody":{"description":"The token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"rst_9k2m4h1p7q"}}}}}},"/profile/customer/social-login":{"post":{"operationId":"UsersController_customerValidateSocialLobin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Tokens and the customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Validate a customer social login","description":"Completes a social sign-in for a customer across providers.\n\n#### Signature\n\n```http\nPOST /profile/customer/social-login (body) -> Tokens and the customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/google/token`","requestBody":{"description":"The provider and its token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"provider":"google","accessToken":"ya29…"}}}}}},"/user/customer/social-login":{"post":{"operationId":"UsersController_customerValidateSocialLobin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Tokens and the customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Validate a customer social login","description":"Completes a social sign-in for a customer across providers.\n\n#### Signature\n\n```http\nPOST /user/customer/social-login (body) -> Tokens and the customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/google/token`","requestBody":{"description":"The provider and its token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"provider":"google","accessToken":"ya29…"}}}}}},"/profile/customer/password/forgot/{email}/{redirectUrl}":{"get":{"operationId":"UsersController_customerPasswordForgot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"},{"name":"redirectUrl","required":true,"in":"path","schema":{"type":"string"},"description":"Where to return the customer after reset.","example":"https%3A%2F%2Fshop.example.com%2Freset"},{"name":"strategy","required":true,"in":"query","schema":{"type":"string"}},{"name":"password","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Request a customer password reset","description":"Sends a customer a reset link, returning them to the supplied URL. **Validate `redirectUrl` against an allowlist** — an unchecked redirect in a password-reset email is a phishing vector.\n\n#### Signature\n\n```http\nGET /profile/customer/password/forgot/{email}/{redirectUrl} (email: string, redirectUrl: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- An open redirect here lands in a trusted email. Restrict the accepted hosts.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/reset`"}},"/user/customer/password/forgot/{email}/{redirectUrl}":{"get":{"operationId":"UsersController_customerPasswordForgot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"},{"name":"redirectUrl","required":true,"in":"path","schema":{"type":"string"},"description":"Where to return the customer after reset.","example":"https%3A%2F%2Fshop.example.com%2Freset"},{"name":"strategy","required":true,"in":"query","schema":{"type":"string"}},{"name":"password","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Request a customer password reset","description":"Sends a customer a reset link, returning them to the supplied URL. **Validate `redirectUrl` against an allowlist** — an unchecked redirect in a password-reset email is a phishing vector.\n\n#### Signature\n\n```http\nGET /user/customer/password/forgot/{email}/{redirectUrl} (email: string, redirectUrl: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- An open redirect here lands in a trusted email. Restrict the accepted hosts.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/password/reset`"}},"/profile/customers/{attr}/{value}":{"get":{"operationId":"UsersController_customers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"attr","required":true,"in":"path","schema":{"type":"string"},"description":"Attribute to match.","example":"city"},{"name":"value","required":true,"in":"path","schema":{"type":"string"},"description":"Value to match.","example":"London"}],"responses":{"200":{"description":"Matching customers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Find customers by attribute","description":"Finds customers matching an attribute and value.\n\n#### Signature\n\n```http\nGET /profile/customers/{attr}/{value} (attr: string, value: string) -> Matching customers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /customer/profile/{emailOrUsername}`"}},"/user/customers/{attr}/{value}":{"get":{"operationId":"UsersController_customers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"attr","required":true,"in":"path","schema":{"type":"string"},"description":"Attribute to match.","example":"city"},{"name":"value","required":true,"in":"path","schema":{"type":"string"},"description":"Value to match.","example":"London"}],"responses":{"200":{"description":"Matching customers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Find customers by attribute","description":"Finds customers matching an attribute and value.\n\n#### Signature\n\n```http\nGET /user/customers/{attr}/{value} (attr: string, value: string) -> Matching customers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /customer/profile/{emailOrUsername}`"}},"/profile/customer/exist/{emailOrUsername}":{"get":{"operationId":"UsersController_customerExist","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"}],"responses":{"200":{"description":"Whether the customer exists","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Check whether a customer exists","description":"Reports whether a customer account exists. This confirms account existence to an unauthenticated caller — rate-limit anything built on it.\n\n#### Signature\n\n```http\nGET /profile/customer/exist/{emailOrUsername} (emailOrUsername: string) -> Whether the customer exists\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- An enumeration surface by design — treat it accordingly.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signup`"}},"/user/customer/exist/{emailOrUsername}":{"get":{"operationId":"UsersController_customerExist","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"}],"responses":{"200":{"description":"Whether the customer exists","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Check whether a customer exists","description":"Reports whether a customer account exists. This confirms account existence to an unauthenticated caller — rate-limit anything built on it.\n\n#### Signature\n\n```http\nGET /user/customer/exist/{emailOrUsername} (emailOrUsername: string) -> Whether the customer exists\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- An enumeration surface by design — treat it accordingly.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/signup`"}},"/profile/customer/profile/{emailOrUsername}":{"get":{"operationId":"UsersController_customerProfile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"}],"responses":{"200":{"description":"The customer profile","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Get a customer profile","description":"Fetches a customer profile by email or username.\n\n#### Signature\n\n```http\nGET /profile/customer/profile/{emailOrUsername} (emailOrUsername: string) -> The customer profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /customer/update`"},"delete":{"operationId":"UsersController_deleteCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"},{"name":"reason","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Delete a customer","description":"Deletes a customer account properly — the endpoint the generic repository delete points at rather than dropping the row.\n\n#### Signature\n\n```http\nDELETE /profile/customer/profile/{emailOrUsername} (emailOrUsername: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/delete/{datatype}/{id}`"}},"/user/customer/profile/{emailOrUsername}":{"get":{"operationId":"UsersController_customerProfile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"}],"responses":{"200":{"description":"The customer profile","content":{"application/json":{"schema":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Get a customer profile","description":"Fetches a customer profile by email or username.\n\n#### Signature\n\n```http\nGET /user/customer/profile/{emailOrUsername} (emailOrUsername: string) -> The customer profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /customer/update`"},"delete":{"operationId":"UsersController_deleteCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"emailOrUsername","required":true,"in":"path","schema":{"type":"string"},"description":"Email address or username.","example":"ada@example.com"},{"name":"reason","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Delete a customer","description":"Deletes a customer account properly — the endpoint the generic repository delete points at rather than dropping the row.\n\n#### Signature\n\n```http\nDELETE /user/customer/profile/{emailOrUsername} (emailOrUsername: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /repository/delete/{datatype}/{id}`"}},"/profile/customer/self":{"delete":{"operationId":"UsersController_deleteSelfCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Delete my customer account","description":"Deletes the calling customer's own account. Irreversible.\n\n#### Signature\n\n```http\nDELETE /profile/customer/self () -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /customer/profile/{emailOrUsername}`"}},"/user/customer/self":{"delete":{"operationId":"UsersController_deleteSelfCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Delete my customer account","description":"Deletes the calling customer's own account. Irreversible.\n\n#### Signature\n\n```http\nDELETE /user/customer/self () -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /customer/profile/{emailOrUsername}`"}},"/profile/blacklist/get":{"get":{"operationId":"UsersController_blacklistGet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Blacklist entries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Get the blacklist","description":"The blocked devices, addresses and API keys. Blocked entries are refused at sign-in before credentials are checked.\n\n#### Signature\n\n```http\nGET /profile/blacklist/get () -> Blacklist entries\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /blacklist/add`"}},"/user/blacklist/get":{"get":{"operationId":"UsersController_blacklistGet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Blacklist entries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Get the blacklist","description":"The blocked devices, addresses and API keys. Blocked entries are refused at sign-in before credentials are checked.\n\n#### Signature\n\n```http\nGET /user/blacklist/get () -> Blacklist entries\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /blacklist/add`"}},"/profile/blacklist/add":{"post":{"operationId":"UsersController_blacklistAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The blacklist entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Add to the blacklist","description":"Blocks a device or identifier from authenticating. Takes effect immediately and refuses sign-in before credentials are evaluated.\n\n#### Signature\n\n```http\nPOST /profile/blacklist/add (body) -> The blacklist entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Blocking broadly — a shared IP, say — can lock out legitimate users.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /blacklist/delete/{value}`","requestBody":{"description":"What to block.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"value":"203.0.113.42","type":"ip","reason":"Credential stuffing"}}}}}},"/user/blacklist/add":{"post":{"operationId":"UsersController_blacklistAdd","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The blacklist entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Add to the blacklist","description":"Blocks a device or identifier from authenticating. Takes effect immediately and refuses sign-in before credentials are evaluated.\n\n#### Signature\n\n```http\nPOST /user/blacklist/add (body) -> The blacklist entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Blocking broadly — a shared IP, say — can lock out legitimate users.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /blacklist/delete/{value}`","requestBody":{"description":"What to block.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"value":"203.0.113.42","type":"ip","reason":"Credential stuffing"}}}}}},"/profile/blacklist/delete/{value}":{"delete":{"operationId":"UsersController_blacklistRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"value","required":true,"in":"path","schema":{"type":"string"},"description":"The blacklisted value.","example":"203.0.113.42"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Remove from the blacklist","description":"Unblocks a previously blacklisted value.\n\n#### Signature\n\n```http\nDELETE /profile/blacklist/delete/{value} (value: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /blacklist/add`"}},"/user/blacklist/delete/{value}":{"delete":{"operationId":"UsersController_blacklistRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"value","required":true,"in":"path","schema":{"type":"string"},"description":"The blacklisted value.","example":"203.0.113.42"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Remove from the blacklist","description":"Unblocks a previously blacklisted value.\n\n#### Signature\n\n```http\nDELETE /user/blacklist/delete/{value} (value: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /blacklist/add`"}},"/profile/blacklist/apikey/add":{"post":{"operationId":"UsersController_blacklistApiKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The blacklist entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Blacklist an API key","description":"Blocks an API key — the immediate response to a leaked credential, and faster than rotating it everywhere.\n\n#### Signature\n\n```http\nPOST /profile/blacklist/apikey/add (body) -> The blacklist entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /blacklist/apikey/delete/{apiKey}`","requestBody":{"description":"The key to block.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"apiKey":"ak_9k2m4h1p7q","reason":"Leaked in a public repository"}}}}}},"/user/blacklist/apikey/add":{"post":{"operationId":"UsersController_blacklistApiKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The blacklist entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Blacklist an API key","description":"Blocks an API key — the immediate response to a leaked credential, and faster than rotating it everywhere.\n\n#### Signature\n\n```http\nPOST /user/blacklist/apikey/add (body) -> The blacklist entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /blacklist/apikey/delete/{apiKey}`","requestBody":{"description":"The key to block.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"apiKey":"ak_9k2m4h1p7q","reason":"Leaked in a public repository"}}}}}},"/profile/blacklist/apikey/delete/{apiKey}":{"delete":{"operationId":"UsersController_unblacklistApiKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"apiKey","required":true,"in":"path","schema":{"type":"string"},"description":"The blocked key.","example":"ak_9k2m4h1p7q"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Unblacklist an API key","description":"Restores a blocked API key.\n\n#### Signature\n\n```http\nDELETE /profile/blacklist/apikey/delete/{apiKey} (apiKey: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /blacklist/apikey/add`"}},"/user/blacklist/apikey/delete/{apiKey}":{"delete":{"operationId":"UsersController_unblacklistApiKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"apiKey","required":true,"in":"path","schema":{"type":"string"},"description":"The blocked key.","example":"ak_9k2m4h1p7q"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Administration"],"summary":"Unblacklist an API key","description":"Restores a blocked API key.\n\n#### Signature\n\n```http\nDELETE /user/blacklist/apikey/delete/{apiKey} (apiKey: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /blacklist/apikey/add`"}},"/profile/code/{email}":{"get":{"operationId":"UsersController_codeLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Request a login code","description":"Sends a one-time login code to an email address — passwordless sign-in.\n\n#### Signature\n\n```http\nGET /profile/code/{email} (email: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit per address and per IP.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /magic-link`"}},"/user/code/{email}":{"get":{"operationId":"UsersController_codeLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"}],"responses":{"200":{"description":"The request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Request a login code","description":"Sends a one-time login code to an email address — passwordless sign-in.\n\n#### Signature\n\n```http\nGET /user/code/{email} (email: string) -> The request result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rate-limit per address and per IP.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /magic-link`"}},"/profile/magic-link":{"get":{"operationId":"UsersController_magicLinkLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-host","required":true,"in":"header","schema":{"type":"string"}},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"email","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"deliveryType","required":true,"in":"query","schema":{"type":"string"}},{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"token","in":"query","required":true,"description":"Magic link token.","schema":{"type":"string"},"example":"mlk_9k2m4h1p7q"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Sign in with a magic link","description":"Completes a passwordless sign-in from an emailed link. The link is a credential — anyone with it is signed in, so keep it short-lived.\n\n#### Signature\n\n```http\nGET /profile/magic-link (token?: string) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /magic-link/redirect`"}},"/user/magic-link":{"get":{"operationId":"UsersController_magicLinkLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-host","required":true,"in":"header","schema":{"type":"string"}},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"email","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"deliveryType","required":true,"in":"query","schema":{"type":"string"}},{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"token","in":"query","required":true,"description":"Magic link token.","schema":{"type":"string"},"example":"mlk_9k2m4h1p7q"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Sign in with a magic link","description":"Completes a passwordless sign-in from an emailed link. The link is a credential — anyone with it is signed in, so keep it short-lived.\n\n#### Signature\n\n```http\nGET /user/magic-link (token?: string) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /magic-link/redirect`"}},"/profile/magic-link/redirect":{"post":{"operationId":"UsersController_magicLinkLoginRedirect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-host","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"The redirect result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Redirect after a magic link","description":"Completes the magic-link flow and redirects the browser onward.\n\n#### Signature\n\n```http\nPOST /profile/magic-link/redirect (body) -> The redirect result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /magic-link`","requestBody":{"description":"The token and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"mlk_9k2m4h1p7q","redirectUrl":"https://app.example.com/"}}}}}},"/user/magic-link/redirect":{"post":{"operationId":"UsersController_magicLinkLoginRedirect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-host","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"The redirect result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Redirect after a magic link","description":"Completes the magic-link flow and redirects the browser onward.\n\n#### Signature\n\n```http\nPOST /user/magic-link/redirect (body) -> The redirect result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /magic-link`","requestBody":{"description":"The token and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"mlk_9k2m4h1p7q","redirectUrl":"https://app.example.com/"}}}}}},"/profile/user/magic-link":{"get":{"operationId":"UsersController_magicLinkUserLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"example":"mlk_9k2m4h1p7q"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Sign in with a magic link (user)","description":"The user-scoped magic-link sign-in, distinct from the customer flow.\n\n#### Signature\n\n```http\nGET /profile/user/magic-link (token?: string) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/magic-link/redirect`"}},"/user/user/magic-link":{"get":{"operationId":"UsersController_magicLinkUserLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"example":"mlk_9k2m4h1p7q"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Sign in with a magic link (user)","description":"The user-scoped magic-link sign-in, distinct from the customer flow.\n\n#### Signature\n\n```http\nGET /user/user/magic-link (token?: string) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /user/magic-link/redirect`"}},"/profile/user/magic-link/redirect":{"post":{"operationId":"UsersController_magicLinkUserRedirect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The redirect result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Redirect after a user magic link","description":"Completes the user magic-link flow and redirects onward.\n\n#### Signature\n\n```http\nPOST /profile/user/magic-link/redirect (body) -> The redirect result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /user/magic-link`","requestBody":{"description":"The token and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"mlk_9k2m4h1p7q"}}}}}},"/user/user/magic-link/redirect":{"post":{"operationId":"UsersController_magicLinkUserRedirect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The redirect result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Redirect after a user magic link","description":"Completes the user magic-link flow and redirects onward.\n\n#### Signature\n\n```http\nPOST /user/user/magic-link/redirect (body) -> The redirect result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /user/magic-link`","requestBody":{"description":"The token and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"mlk_9k2m4h1p7q"}}}}}},"/profile/facebook/url":{"get":{"operationId":"UsersController_facebookAuthUrl","summary":"Get the Facebook auth URL","description":"Returns the URL to send a browser to for Facebook sign-in, for clients that redirect themselves.\n\n#### Signature\n\n```http\nGET /profile/facebook/url () -> The auth URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /facebook`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The auth URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/facebook/url":{"get":{"operationId":"UsersController_facebookAuthUrl","summary":"Get the Facebook auth URL","description":"Returns the URL to send a browser to for Facebook sign-in, for clients that redirect themselves.\n\n#### Signature\n\n```http\nGET /user/facebook/url () -> The auth URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /facebook`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The auth URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/facebook":{"get":{"operationId":"UsersController_facebookLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Redirect to Facebook","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Start Facebook sign-in","description":"Begins the Facebook OAuth flow.\n\n#### Signature\n\n```http\nGET /profile/facebook () -> Redirect to Facebook\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /facebook/redirect`"}},"/user/facebook":{"get":{"operationId":"UsersController_facebookLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Redirect to Facebook","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Start Facebook sign-in","description":"Begins the Facebook OAuth flow.\n\n#### Signature\n\n```http\nGET /user/facebook () -> Redirect to Facebook\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /facebook/redirect`"}},"/profile/facebook/redirect":{"get":{"operationId":"UsersController_facebookLoginCallback","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Facebook sign-in callback","description":"Handles Facebook's OAuth callback and issues tokens.\n\n#### Signature\n\n```http\nGET /profile/facebook/redirect () -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /facebook/token`"}},"/user/facebook/redirect":{"get":{"operationId":"UsersController_facebookLoginCallback","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Facebook sign-in callback","description":"Handles Facebook's OAuth callback and issues tokens.\n\n#### Signature\n\n```http\nGET /user/facebook/redirect () -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /facebook/token`"}},"/profile/facebook/token":{"post":{"operationId":"UsersController_facebookTokenExchange","summary":"Exchange a Facebook token","description":"Exchanges a Facebook access token for platform tokens — the native-app flow, where the SDK obtains the token rather than a browser redirect.\n\n#### Signature\n\n```http\nPOST /profile/facebook/token (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Verify the token with the provider — a client-supplied token is not evidence on its own.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /facebook/redirect`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The provider token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"accessToken":"EAAG…"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/facebook/token":{"post":{"operationId":"UsersController_facebookTokenExchange","summary":"Exchange a Facebook token","description":"Exchanges a Facebook access token for platform tokens — the native-app flow, where the SDK obtains the token rather than a browser redirect.\n\n#### Signature\n\n```http\nPOST /user/facebook/token (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Verify the token with the provider — a client-supplied token is not evidence on its own.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /facebook/redirect`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The provider token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"accessToken":"EAAG…"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/google":{"get":{"operationId":"UsersController_googleLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Redirect to Google","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Start Google sign-in","description":"Begins the Google OAuth flow.\n\n#### Signature\n\n```http\nGET /profile/google () -> Redirect to Google\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /google/redirect`"}},"/user/google":{"get":{"operationId":"UsersController_googleLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Redirect to Google","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Start Google sign-in","description":"Begins the Google OAuth flow.\n\n#### Signature\n\n```http\nGET /user/google () -> Redirect to Google\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /google/redirect`"}},"/profile/google/redirect":{"get":{"operationId":"UsersController_googleLoginCallback","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Google sign-in callback","description":"Handles Google's OAuth callback and issues tokens.\n\n#### Signature\n\n```http\nGET /profile/google/redirect () -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/google/token`"}},"/user/google/redirect":{"get":{"operationId":"UsersController_googleLoginCallback","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Google sign-in callback","description":"Handles Google's OAuth callback and issues tokens.\n\n#### Signature\n\n```http\nGET /user/google/redirect () -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/google/token`"}},"/profile/customer/google/token":{"post":{"operationId":"UsersController_customerGoogleTokenExchange","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Tokens and the customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Exchange a Google token for a customer","description":"Exchanges a Google token for customer tokens — the native-app sign-in path.\n\n#### Signature\n\n```http\nPOST /profile/customer/google/token (body) -> Tokens and the customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Verify the token with Google rather than trusting the client.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/social-login`","requestBody":{"description":"The provider token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"idToken":"eyJhbGciOi…"}}}}}},"/user/customer/google/token":{"post":{"operationId":"UsersController_customerGoogleTokenExchange","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Tokens and the customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Customers"],"summary":"Exchange a Google token for a customer","description":"Exchanges a Google token for customer tokens — the native-app sign-in path.\n\n#### Signature\n\n```http\nPOST /user/customer/google/token (body) -> Tokens and the customer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Verify the token with Google rather than trusting the client.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /customer/social-login`","requestBody":{"description":"The provider token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"idToken":"eyJhbGciOi…"}}}}}},"/profile/github/url":{"get":{"operationId":"UsersController_githubAuthUrl","summary":"Get the GitHub auth URL","description":"Returns the URL to send a browser to for GitHub sign-in.\n\n#### Signature\n\n```http\nGET /profile/github/url () -> The auth URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /github`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The auth URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/github/url":{"get":{"operationId":"UsersController_githubAuthUrl","summary":"Get the GitHub auth URL","description":"Returns the URL to send a browser to for GitHub sign-in.\n\n#### Signature\n\n```http\nGET /user/github/url () -> The auth URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /github`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The auth URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/github":{"get":{"operationId":"UsersController_githubLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Redirect to GitHub","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Start GitHub sign-in","description":"Begins the GitHub OAuth flow.\n\n#### Signature\n\n```http\nGET /profile/github () -> Redirect to GitHub\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /github/redirect`"}},"/user/github":{"get":{"operationId":"UsersController_githubLogin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Redirect to GitHub","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Start GitHub sign-in","description":"Begins the GitHub OAuth flow.\n\n#### Signature\n\n```http\nGET /user/github () -> Redirect to GitHub\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /github/redirect`"}},"/profile/github/redirect":{"get":{"operationId":"UsersController_githubLoginCallback","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"GitHub sign-in callback","description":"Handles GitHub's OAuth callback and issues tokens.\n\n#### Signature\n\n```http\nGET /profile/github/redirect () -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /github/redirect`"},"post":{"operationId":"UsersController_githubLoginCallbackPost","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"GitHub sign-in callback (POST)","description":"The POST form of the GitHub callback, for clients that post the authorisation code rather than being redirected.\n\n#### Signature\n\n```http\nPOST /profile/github/redirect (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /github/redirect`","requestBody":{"description":"The authorisation code.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"abc123"}}}}}},"/user/github/redirect":{"get":{"operationId":"UsersController_githubLoginCallback","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"GitHub sign-in callback","description":"Handles GitHub's OAuth callback and issues tokens.\n\n#### Signature\n\n```http\nGET /user/github/redirect () -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /github/redirect`"},"post":{"operationId":"UsersController_githubLoginCallbackPost","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"GitHub sign-in callback (POST)","description":"The POST form of the GitHub callback, for clients that post the authorisation code rather than being redirected.\n\n#### Signature\n\n```http\nPOST /user/github/redirect (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /github/redirect`","requestBody":{"description":"The authorisation code.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"abc123"}}}}}},"/profile/github/token":{"post":{"operationId":"UsersController_githubTokenExchange","summary":"Exchange a GitHub token","description":"Exchanges a GitHub access token for platform tokens.\n\n#### Signature\n\n```http\nPOST /profile/github/token (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /facebook/token`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The provider token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"accessToken":"gho_…"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/user/github/token":{"post":{"operationId":"UsersController_githubTokenExchange","summary":"Exchange a GitHub token","description":"Exchanges a GitHub access token for platform tokens.\n\n#### Signature\n\n```http\nPOST /user/github/token (body) -> Tokens and the signed-in user\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /facebook/token`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The provider token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"accessToken":"gho_…"}}}},"responses":{"201":{"description":"Tokens and the signed-in user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Bearer token for the `Authorization` header (a JWT; its lifetime is set by the server). Treat as a credential."},"refreshToken":{"type":"string","description":"Used to obtain a new token. Longer-lived, so more sensitive."},"user":{"type":"object","description":"A user or customer record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"username":{"type":"string","example":"ada"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"roles":{"type":"array","items":{"type":"string"},"example":["User"]},"groups":{"type":"array","items":{"type":"string"},"example":["engineering"]},"status":{"type":"string","example":"active"}}}}},"orgId":{"type":"string","description":"The org the session is for.","example":"acme"},"rootOrg":{"type":"string","description":"The platform root org id."},"sharedOrg":{"type":"string","description":"The platform shared org id."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"]}},"/profile/auth/success":{"get":{"operationId":"UsersController_authSuccess","parameters":[{"name":"token","required":true,"in":"query","schema":{"type":"string"}},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Authentication result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Authentication success callback","description":"The landing endpoint after a successful external authentication flow.\n\n#### Signature\n\n```http\nGET /profile/auth/success () -> Authentication result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /google/redirect`"}},"/user/auth/success":{"get":{"operationId":"UsersController_authSuccess","parameters":[{"name":"token","required":true,"in":"query","schema":{"type":"string"}},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Authentication result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Authentication"],"summary":"Authentication success callback","description":"The landing endpoint after a successful external authentication flow.\n\n#### Signature\n\n```http\nGET /user/auth/success () -> Authentication result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /google/redirect`"}},"/api-key/create":{"post":{"operationId":"ApikeyController_createApiKey","summary":"Create an API key","description":"Creates an API key for machine-to-machine access.\n\nThe key value is returned **once, at creation**. It cannot be retrieved afterwards — if it is lost, regenerate rather than hunting for it. Scope the key to what the integration actually needs.\n\n#### Signature\n\n```http\nPOST /api-key/create (body) -> The created key — the secret is shown only here\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Set an expiry. A key with no expiry outlives whoever created it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /api-key/regenerate/{keyId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The key to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Warehouse sync"},"scopes":{"type":"array","items":{"type":"string"},"example":["storefront:read"]},"expiresAt":{"type":"string","format":"date-time"}}},"example":{"name":"Warehouse sync","scopes":["storefront:read"],"expiresAt":"2027-01-01T00:00:00.000Z"}}}},"responses":{"201":{"description":"The created key — the secret is shown only here","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]}},"/api-key/list":{"get":{"operationId":"ApikeyController_getUserApiKeys","summary":"List my API keys","description":"The caller's API keys — names, scopes and last use, but never the secrets.\n\n#### Signature\n\n```http\nGET /api-key/list () -> API keys\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /api-key/usage/{keyId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"API keys","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]}},"/api-key/{keyId}":{"get":{"operationId":"ApikeyController_getApiKey","summary":"Get an API key","description":"Fetches one key's metadata. The secret is not returned.\n\n#### Signature\n\n```http\nGET /api-key/{keyId} (keyId: string) -> The key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /api-key/{keyId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"keyId","required":true,"in":"path","description":"API key id.","schema":{"type":"string"},"example":"AKY-4821"}],"responses":{"200":{"description":"The key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]},"put":{"operationId":"ApikeyController_updateApiKey","summary":"Update an API key","description":"Updates a key's name, scopes or expiry. Narrowing scopes takes effect immediately, so check what the integration relies on first.\n\n#### Signature\n\n```http\nPUT /api-key/{keyId} (keyId: string, body) -> The updated key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /api-key/{keyId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"keyId","required":true,"in":"path","description":"API key id.","schema":{"type":"string"},"example":"AKY-4821"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Warehouse sync (read-only)","scopes":["storefront:read"]}}}},"responses":{"200":{"description":"The updated key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]},"delete":{"operationId":"ApikeyController_deleteApiKey","summary":"Delete an API key","description":"Deletes a key permanently. Anything using it stops working immediately — check the usage report before deleting a key you did not create.\n\n#### Signature\n\n```http\nDELETE /api-key/{keyId} (keyId: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Immediate and irreversible. Check `usage/{keyId}` first.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /api-key/usage/{keyId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"keyId","required":true,"in":"path","description":"API key id.","schema":{"type":"string"},"example":"AKY-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]}},"/api-key/regenerate/{keyId}":{"post":{"operationId":"ApikeyController_regenerateApiKey","summary":"Regenerate an API key","description":"Issues a new secret for an existing key and invalidates the old one, keeping the key's name and scopes.\n\n**The old secret stops working the moment this returns.** Have the new value ready to deploy before calling it, or the integration breaks in the gap.\n\n#### Signature\n\n```http\nPOST /api-key/regenerate/{keyId} (keyId: string) -> The key with its new secret — shown once\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No overlap period. Plan the cutover before regenerating.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /api-key/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"keyId","required":true,"in":"path","description":"API key id.","schema":{"type":"string"},"example":"AKY-4821"}],"responses":{"201":{"description":"The key with its new secret — shown once","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]}},"/api-key/usage/{keyId}":{"get":{"operationId":"ApikeyController_getApiKeyUsage","summary":"Get API key usage","description":"How a key has been used — request volume and last activity. Read this before revoking a key to see what depends on it, and to spot a key still live long after its integration was retired.\n\n#### Signature\n\n```http\nGET /api-key/usage/{keyId} (keyId: string) -> Key usage\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /api-key/{keyId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"keyId","required":true,"in":"path","description":"API key id.","schema":{"type":"string"},"example":"AKY-4821"}],"responses":{"200":{"description":"Key usage","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]}},"/api-key/admin/list":{"get":{"operationId":"ApikeyController_getAllApiKeys","summary":"List all API keys (admin)","description":"Every API key in the organization, not just the caller's — the audit view for finding forgotten or over-scoped keys.\n\n#### Signature\n\n```http\nGET /api-key/admin/list () -> All API keys\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Admin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /api-key/admin/revoke/{keyId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"All API keys","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"]}},"/api-key/admin/revoke/{keyId}":{"post":{"operationId":"ApikeyController_revokeApiKey","summary":"Revoke an API key (admin)","description":"Revokes any key in the organization, regardless of who created it — the response to a leaked credential when its owner is unavailable.\n\n#### Signature\n\n```http\nPOST /api-key/admin/revoke/{keyId} (keyId: string, body) -> The revoked key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Admin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /blacklist/apikey/add`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"keyId","required":true,"in":"path","description":"API key id.","schema":{"type":"string"},"example":"AKY-4821"}],"responses":{"201":{"description":"The revoked key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"],"requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Leaked in a public repository"}}}}}},"/api-key/auth":{"post":{"operationId":"ApikeyController_authenticateApiKey","summary":"Authenticate with an API key","description":"Exchanges an API key for an access token. The public entry point for machine-to-machine callers — the key is the credential, so it belongs in a header or body, never a URL.\n\n#### Signature\n\n```http\nPOST /api-key/auth (body) -> An access token\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | INVALID_KEY | Invalid or revoked API key | The key is unknown, expired, revoked or blacklisted. | Check the key is still active with the usage endpoint. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /validate-app-key`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-api-key","required":true,"in":"header","description":"API Key","schema":{"type":"string"}}],"responses":{"201":{"description":"An access token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Invalid or revoked API key — The key is unknown, expired, revoked or blacklisted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Invalid or revoked API key","path":"/api-key/auth","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"],"requestBody":{"description":"The API key.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"ak_9k2m4h1p7q"}}},"example":{"key":"ak_9k2m4h1p7q"}}}}}},"/api-key/org/{orgid}":{"get":{"operationId":"ApikeyController_getOrgUsers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"Organization id.","example":"acme-retail"}],"responses":{"200":{"description":"API keys","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"],"summary":"List an organization's API keys","description":"API keys for a named organization. Cross-org, so restrict to operators who legitimately administer more than one tenant.\n\n#### Signature\n\n```http\nGET /api-key/org/{orgid} (orgid: string) -> API keys\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /api-key/org/{orgid}/create`"}},"/api-key/org/{orgid}/create":{"post":{"operationId":"ApikeyController_createOrgUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"Organization id.","example":"acme-retail"}],"responses":{"201":{"description":"The created key","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"],"summary":"Create an API key for an organization","description":"Creates a key against a named organization rather than the caller's own. The secret is returned once.\n\n#### Signature\n\n```http\nPOST /api-key/org/{orgid}/create (orgid: string, body) -> The created key\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /api-key/org/{orgid}/delete`","requestBody":{"description":"The key to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Partner integration","scopes":["storefront:read"]}}}}}},"/api-key/org/{orgid}/delete":{"post":{"operationId":"ApikeyController_deleteOrgUsers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"Organization id.","example":"acme-retail"}],"responses":{"201":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · API keys"],"summary":"Delete an organization API key","description":"Deletes a key belonging to a named organization. Note this is a `POST`, not a `DELETE`, unlike the self-service equivalent.\n\n#### Signature\n\n```http\nPOST /api-key/org/{orgid}/delete (orgid: string, body) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /api-key/{keyId}`","requestBody":{"description":"Which key to delete.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"keyId":"AKY-4821"}}}}}},"/upstream/integration-types/{type}":{"get":{"operationId":"UpstreamController_getIntegrationTypes","summary":"Get integration types","description":"The integration types the platform supports and what each provides. `type` narrows to one; omit it for the full catalogue.\n\n#### Signature\n\n```http\nGET /upstream/integration-types/{type} (type: string) -> Integration types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /upstream/integration-use-cases/{useCase}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"type","required":true,"in":"path","description":"Integration type. Optional.","schema":{"type":"string"},"example":"payment"}],"responses":{"200":{"description":"Integration types","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/integration-use-cases/{useCase}":{"get":{"operationId":"UpstreamController_getIntegrationUseCases","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"useCase","required":true,"in":"path","schema":{"type":"string"},"description":"Use case name.","example":"sms"}],"responses":{"200":{"description":"Integrations for the use case","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"],"summary":"Get integrations for a use case","description":"Which integrations serve a given use case — the lookup for \"what can send SMS\" rather than \"what does Twilio do\".\n\n#### Signature\n\n```http\nGET /upstream/integration-use-cases/{useCase} (useCase: string) -> Integrations for the use case\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /upstream/integration-types/{type}`"}},"/upstream/get-config/{type}/{configId}":{"get":{"operationId":"UpstreamController_getIntegrationConfig","summary":"Get an integration configuration","description":"Reads an integration's configuration. Both path segments are optional — narrow by type, or fetch one configuration by id.\n\n`create=true` **creates the configuration if it does not exist**, which makes this a write in disguise. Leave it off for a plain read.\n\nConfiguration carries credentials for the external service; treat the response as sensitive.\n\n#### Signature\n\n```http\nGET /upstream/get-config/{type}/{configId} (type: string, configId: string, create?: boolean) -> The configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `create=true` writes.\n- Response may contain credentials.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /upstream/save-integration`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"type","required":true,"in":"path","description":"Integration type. Optional.","schema":{"type":"string"},"example":"payment"},{"name":"configId","required":true,"in":"path","description":"Configuration id. Optional.","schema":{"type":"string"},"example":"CFG-12"},{"name":"create","required":false,"in":"query","description":"Create the configuration if missing. Default false.","schema":{"type":"boolean"},"example":false}],"responses":{"200":{"description":"The configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/active/{type}":{"get":{"operationId":"UpstreamController_listActiveIntegrationByType","summary":"List active integrations","description":"Integrations currently connected for the org, optionally narrowed to one type. The list to read before offering an integration-backed feature.\n\n#### Signature\n\n```http\nGET /upstream/active/{type} (type: string) -> Active integrations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /upstream/active/detail/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"type","required":true,"in":"path","description":"Integration type. Optional.","schema":{"type":"string"},"example":"payment"}],"responses":{"200":{"description":"Active integrations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/active/detail/{id}":{"get":{"operationId":"UpstreamController_activeIntegrations","summary":"Get an active integration","description":"Details of one active integration.\n\n#### Signature\n\n```http\nGET /upstream/active/detail/{id} (id: string) -> The integration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /upstream/active/{type}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Integration id. Optional.","schema":{"type":"string"},"example":"INT-12"}],"responses":{"200":{"description":"The integration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/get-integration":{"post":{"operationId":"UpstreamController_getIntegration","summary":"Get an integration by id","description":"Fetches an integration by identifier. A POST because the selector goes in the body; it is a read.\n\n#### Signature\n\n```http\nPOST /upstream/get-integration (body) -> The integration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only despite being a POST.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /upstream/active/detail/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which integration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"INT-12"}}}},"responses":{"201":{"description":"The integration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/shutdown/{id}":{"post":{"operationId":"UpstreamController_shutdown","summary":"Shut down an integration","description":"Disconnects an integration. Everything depending on it stops working immediately — payments, messaging, sync — so check what uses it first.\n\n#### Signature\n\n```http\nPOST /upstream/shutdown/{id} (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Breaks every feature that depends on it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /upstream/active/{type}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Integration id.","schema":{"type":"string"},"example":"INT-12"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/call/{integration}/{operation}":{"post":{"operationId":"UpstreamController_callPost","summary":"Call an integration operation","description":"Forwards a call to an external service. The body is passed through as the operation's parameters, and the response is whatever the service returns — this API does not interpret either.\n\nBecause it is a generic proxy, the operation can have any effect the external service supports, including charging money or sending messages. Know what the operation does before calling it.\n\n### User account provisioning\n\nThe `MicrosoftProvider`, `GoogleProvider` and `SlackProvider` integrations create, suspend and remove staff accounts with one shape, so onboarding and offboarding call any of them the same way. Operation parameters go in `data`.\n\n**Common operations** (all three): `createUser`, `getUser`, `updateUser`, `suspendUser`, `resumeUser`, `deleteUser`, `addUserToGroup`, `removeUserFromGroup`, `listGroups`, `provisioningCapabilities`.\n**Microsoft only:** `assignLicense` (`skuId` or skuPartNumber; `removeSkuId`), `listLicenses`, `revokeSignInSessions` (alias `signOutUser`).\n**Google only:** `listOrgUnits`, `signOutUser`.\n**Slack only:** `deactivateUser` / `reactivateUser` (what `suspendUser` / `resumeUser` do), `inviteUser` (Enterprise Grid).\n\n**Input** to `createUser`: `{ email, firstName, lastName, displayName?, password?, groups?, license?, orgUnit?, attributes? }`. `groups` are ids, emails or names. `license` is Microsoft only, `orgUnit` Google only; `attributes` are provider-native fields merged into the request. Other operations identify the account with `{ externalId }` or `{ email }`; group operations add `groupId`.\n\n**Output:** `{ provider, externalId, email, status: \"active\" | \"suspended\" | \"deleted\", alreadyExisted?, temporaryPassword?, warnings?, raw? }`.\n\n**Behaviour**\n- `createUser` is idempotent by email: an existing account is returned untouched with `alreadyExisted: true`.\n- Without `password`, Microsoft and Google get a generated temporary password that must be changed at first sign-in; it is returned once as `temporaryPassword` and never stored or logged. Slack sets no password.\n- A failed licence or group step does not undo the account; it is reported in `warnings`.\n- `deleteUser` requires `confirm: true`. Microsoft keeps deleted users restorable for 30 days, Google for 20; Slack cannot delete, so the account is deactivated.\n- `suspendUser` with `signOut: true` also ends sessions (Microsoft revokeSignInSessions, Google signOut).\n- `provisioningCapabilities` reports, per operation, what the connected credentials and plan allow, and what is missing.\n\n**Setup and permissions**\n- Microsoft (Graph v1.0): `tenantId`, `clientId`, `clientSecret` of an Entra app with admin-consented *Application* permissions User.ReadWrite.All, GroupMember.ReadWrite.All (or Group.ReadWrite.All), Group.Read.All, LicenseAssignment.ReadWrite.All, Organization.Read.All, User.RevokeSessions.All. Directory.ReadWrite.All does not allow deleting users. Optional `defaultUsageLocation` (needed before a licence); `directoryAuth: \"delegated\"` uses the connected admin token instead.\n- Google (Admin SDK Directory API): `workspaceServiceAccountJson` and `workspaceAdminEmail` (a super admin to impersonate); optional `workspaceCustomerId`, `workspaceDomain`, `workspaceDefaultOrgUnit`. Authorise the service account client id for domain-wide delegation with admin.directory.user, admin.directory.group, admin.directory.orgunit and admin.directory.user.security. Alternatively an admin reconnects Google through `getAuthUrl` with `{ workspaceDirectory: true }`.\n- Slack (SCIM v2): Business+ or Enterprise Grid plan; `scimToken` is a user token (xoxp-) with the `admin` scope from a Workspace Owner/Admin (Org Owner on Grid). `inviteUser` (admin.users.invite) is Enterprise Grid only: `adminToken` with admin.users:write, `inviteTeamId`, `defaultInviteChannelIds`. A plan or token that does not allow SCIM fails with a message saying so.\n\n### E-Verify (`EverifyProvider`)\n\nSave a config with `provider: \"EverifyProvider\"` through `POST /upstream/save-integration`. Fields: `environment` (`stage` = E-Verify test account, `production` = live), `companyId` (the employer's E-Verify company ID), and the Web Services credentials E-Verify issued — `username` + `password`, or `clientId` + `clientSecret`. Optional: `baseUrl` (override the standard URL for the environment), `caseCreatorName` / `caseCreatorEmail` / `caseCreatorPhone` (used when no user is signed in), `autoCloseAuthorized` (default true). A config in the shared org with `employerAgent: true` lends its credentials to an org whose own config has only `companyId`.\n\nSaving the config sends every E-Verify case that was waiting for it. Cases are worked through `/business-made/everify/*`, not through `call/*`; the operations (`authenticate`, `createCase`, `submitCase`, `getCase`, `closeCase`, `confirmEmployeeNotified`, `referCase`, `confirmPhotoMatch`, `test`) are available on the proxy for diagnosis — `test` only signs in.\n\n#### Signature\n\n```http\nPOST /upstream/call/{integration}/{operation} (integration: string, operation: string, body) -> The operation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Passthrough — effects are defined by the external service.\n- Provisioning operations create, suspend and delete real accounts at the provider.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Microsoft 365: deleteUser requires \"confirm\": true — this removes the account. | `deleteUser` without `confirm: true`. | Send `confirm: true` once the removal is intended. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /upstream/test/{integration}/{operation}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"integration","required":true,"in":"path","description":"Integration name.","schema":{"type":"string"},"example":"stripe"},{"name":"operation","required":true,"in":"path","description":"Operation name.","schema":{"type":"string"},"example":"listCharges"}],"requestBody":{"description":"Operation parameters, passed through unchanged. Provisioning operations take them under `data`.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"generic":{"summary":"Any operation","value":{"limit":10}},"microsoftCreate":{"summary":"Microsoft 365 — create a user with a licence (POST /upstream/call/MicrosoftProvider/createUser)","value":{"data":{"email":"ana.ng@contoso.com","firstName":"Ana","lastName":"Ng","license":"O365_BUSINESS_PREMIUM","groups":["Kitchen Staff"]}}},"googleCreate":{"summary":"Google Workspace — create a user in an org unit (POST /upstream/call/GoogleProvider/createUser)","value":{"data":{"email":"ana.ng@acme.com","firstName":"Ana","lastName":"Ng","orgUnit":"/Staff/Kitchen","groups":["kitchen@acme.com"]}}},"suspend":{"summary":"Any provider — suspend and sign out at the end time (…/suspendUser)","value":{"data":{"email":"ana.ng@acme.com","signOut":true}}},"delete":{"summary":"Any provider — delete, which must be confirmed (…/deleteUser)","value":{"data":{"externalId":"0d5b7e2a-1f4c-4b8e-9a61-2f3c4d5e6f70","confirm":true}}},"slackInvite":{"summary":"Slack Enterprise Grid — invite (POST /upstream/call/SlackProvider/inviteUser)","value":{"data":{"email":"ana.ng@acme.com","firstName":"Ana","lastName":"Ng","channelIds":"C0123ABCD"}}},"capabilities":{"summary":"Any provider — what the connected account can do (…/provisioningCapabilities)","value":{"data":{}}}}}}},"responses":{"201":{"description":"The operation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"provisioned":{"summary":"createUser result","value":{"provider":"google","externalId":"104512345678901234567","email":"ana.ng@acme.com","status":"active","alreadyExisted":false,"temporaryPassword":"<returned once>"}}}}}},"400":{"description":"Microsoft 365: deleteUser requires \"confirm\": true — this removes the account. — `deleteUser` without `confirm: true`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Microsoft 365: deleteUser requires \"confirm\": true — this removes the account.","path":"/upstream/call/{integration}/{operation}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]},"get":{"operationId":"UpstreamController_callGet","summary":"Call an integration operation (GET)","description":"The GET form of the proxy, for operations whose parameters fit in a query string. Same passthrough semantics — and note that being a GET does not make the operation safe; that depends on the external service.\n\n#### Signature\n\n```http\nGET /upstream/call/{integration}/{operation} (integration: string, operation: string) -> The operation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A GET here can still have side effects upstream.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /upstream/call/{integration}/{operation}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"integration","required":true,"in":"path","description":"Integration name.","schema":{"type":"string"},"example":"stripe"},{"name":"operation","required":true,"in":"path","description":"Operation name.","schema":{"type":"string"},"example":"listCharges"}],"responses":{"200":{"description":"The operation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/service/{serviceName}/{operation}":{"get":{"operationId":"UpstreamController_serviceGet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serviceName","required":true,"in":"path","schema":{"type":"string"},"description":"Service name.","example":"sms"},{"name":"operation","required":true,"in":"path","schema":{"type":"string"},"description":"Operation name.","example":"listMessages"}],"responses":{"200":{"description":"The operation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"],"summary":"Call a service operation (GET)","description":"Calls an operation by **service name** rather than integration id — the platform resolves which configured integration provides that service. Use it when the caller cares about the capability, not the vendor.\n\n#### Signature\n\n```http\nGET /upstream/service/{serviceName}/{operation} (serviceName: string, operation: string) -> The operation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /upstream/service/{serviceName}/{operation}`"},"post":{"operationId":"UpstreamController_servicePost","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"integration","required":true,"in":"path","schema":{"type":"string"}},{"name":"operation","required":true,"in":"path","schema":{"type":"string"},"description":"Operation name.","example":"send"},{"name":"serviceName","in":"path","required":true,"description":"Service name — the first path segment.","schema":{"type":"string"},"example":"sms"}],"responses":{"201":{"description":"The operation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"],"summary":"Call a service operation","description":"The POST form of the service-name call. Same resolution: the platform picks the integration providing the named service.\n\n**Note:** the handler declares its path parameters as `integration` and `operation` while the route declares `serviceName` and `operation` — the first segment binds to whichever name the route uses, so pass the service name in the first position.\n\n#### Signature\n\n```http\nPOST /upstream/service/{serviceName}/{operation} (serviceName: string, operation: string, body) -> The operation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Passthrough — effects are defined by the external service.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /upstream/service/{serviceName}/{operation}`","requestBody":{"description":"Operation parameters.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"to":"+15551234567","body":"Hello"}}}}}},"/upstream/test/{integration}/{operation}":{"post":{"operationId":"UpstreamController_test","summary":"Test an integration operation","description":"Runs an operation as a connectivity and credential check. The call still reaches the external service — \"test\" means it is run for diagnosis, not that it is simulated, so pick a read-only operation.\n\n#### Signature\n\n```http\nPOST /upstream/test/{integration}/{operation} (integration: string, operation: string, body) -> The test result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Really calls the external service.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /upstream/call/{integration}/{operation}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"integration","required":true,"in":"path","description":"Integration name.","schema":{"type":"string"},"example":"stripe"},{"name":"operation","required":true,"in":"path","description":"Operation name.","schema":{"type":"string"},"example":"listCharges"}],"requestBody":{"description":"Operation parameters.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}},"responses":{"201":{"description":"The test result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/upstream/save-integration":{"post":{"operationId":"UpstreamController_saveIntegration","summary":"Save an integration","description":"Creates or updates an integration configuration, including its credentials. **Never log this request body.** Saving a bad credential does not fail here — it fails on the next call through the integration, so test afterwards.\n\n#### Signature\n\n```http\nPOST /upstream/save-integration (body) -> The saved integration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Carries credentials.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /upstream/test/{integration}/{operation}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The integration configuration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"type":"payment","name":"stripe","config":{"apiKey":"<secret>"}}}}},"responses":{"201":{"description":"The saved integration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Upstream"]}},"/connect/webhook/{vendor}/{serviceId}":{"post":{"operationId":"ConnectController_connectPartnerPost","summary":"Partner webhook (POST)","description":"Receives a webhook from a partner service. **Unauthenticated** — it has to be, since the partner has no session — so the tenant is taken from the `orgid` **query parameter** and the payload's authenticity is whatever the vendor's own signing provides.\n\nTreat the body as untrusted input. `serviceId` is optional and identifies which configured connection the callback belongs to.\n\n#### Signature\n\n```http\nPOST /connect/webhook/{vendor}/{serviceId} (vendor: string, serviceId: string, orgid?: string, body) -> The vendor-appropriate acknowledgement\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated; tenant comes from the query string.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /connect/webhook/{vendor}/{serviceId}`","parameters":[{"name":"orgid","required":true,"in":"query","description":"The tenant — this route does not use the `orgid` header.","schema":{"type":"string"},"example":"org_4821"},{"name":"vendor","required":true,"in":"path","description":"Partner name.","schema":{"type":"string"},"example":"stripe"},{"name":"serviceId","required":true,"in":"path","description":"Which configured connection. Optional.","schema":{"type":"string"},"example":"SVC-12"},{"name":"orgid","in":"header","description":"Organization ID","required":false,"schema":{"type":"string"}}],"requestBody":{"description":"The vendor's payload, passed through as sent.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The vendor-appropriate acknowledgement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Connect"]},"get":{"operationId":"ConnectController_connectPartnerGet","summary":"Partner webhook (GET)","description":"The GET form, for vendors that verify a webhook endpoint with a challenge request before sending real events. Same unauthenticated, query-scoped shape as the POST form.\n\n#### Signature\n\n```http\nGET /connect/webhook/{vendor}/{serviceId} (vendor: string, serviceId: string, orgid?: string) -> The challenge response\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /connect/webhook/{vendor}/{serviceId}`","parameters":[{"name":"orgid","required":true,"in":"query","schema":{"type":"string"},"example":"org_4821"},{"name":"vendor","required":true,"in":"path","description":"Partner name.","schema":{"type":"string"},"example":"facebook"},{"name":"serviceId","required":true,"in":"path","description":"Optional.","schema":{"type":"string"},"example":"SVC-12"},{"name":"orgid","in":"header","description":"Organization ID","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The challenge response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Connect"]}},"/connect/oauth2callback/{vendor}":{"get":{"operationId":"ConnectController_oAuthCallback","summary":"OAuth callback","description":"The redirect target for an OAuth authorization flow. The browser arrives here with `code` and `state`; the server exchanges the code for tokens and stores them against the integration.\n\n`state` is what ties the callback back to the flow that started it — a callback with a mismatched state is not the one that was initiated.\n\n#### Signature\n\n```http\nGET /connect/oauth2callback/{vendor} (vendor: string, code?: string, state?: string) -> A redirect back into the application\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Browser-facing redirect target.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /upstream/save-integration`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization ID","schema":{"type":"string"}},{"name":"vendor","required":true,"in":"path","description":"Partner name.","schema":{"type":"string"},"example":"google"},{"name":"serviceId","required":true,"in":"path","schema":{"type":"string"}},{"name":"state","required":true,"in":"query","description":"Opaque state tying the callback to the initiating flow.","schema":{"type":"string"},"example":"st_7Kq2M9"},{"name":"code","required":true,"in":"query","description":"Authorization code from the provider.","schema":{"type":"string"},"example":"4/0AbC…"}],"responses":{"200":{"description":"A redirect back into the application","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"302":{"description":"Redirect to another URL"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Connect"]}},"/connect/automation/{id}/{stepId}/{entity}/{activity}":{"post":{"operationId":"ConnectController_handleUnifiedAutomationWebhook","summary":"Automation webhook (POST)","description":"The unified inbound hook that lets an external event advance an automation — `id` and `stepId` name where in the automation to resume, `entity` and `activity` say what happened.\n\n**Unauthenticated**, with the tenant in the `orgId` query parameter. Anyone who knows the URL can trigger the step, so treat these URLs as capability tokens and do not publish them. `redirect` sends the caller onward afterwards, which is how this is used from an email link.\n\n#### Signature\n\n```http\nPOST /connect/automation/{id}/{stepId}/{entity}/{activity} (id: string, stepId: string, entity: string, activity: string, orgId?: string, automationId?: string, redirect?: string, body) -> A result, or a redirect\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the URL is the credential.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /connect/automation/{id}/{stepId}/{entity}/{activity}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"orgId","required":true,"in":"query","description":"The tenant.","schema":{"type":"string"},"example":"org_4821"},{"name":"entity","required":true,"in":"path","description":"What the event concerns.","schema":{"type":"string"},"example":"customer"},{"name":"activity","required":true,"in":"path","description":"What happened.","schema":{"type":"string"},"example":"clicked"},{"name":"stepId","required":false,"in":"query","description":"Step ID","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Automation id.","schema":{"type":"string"},"example":"AUT-4821"},{"name":"automationId","required":false,"in":"query","schema":{"type":"string"},"example":"AUT-4821"},{"name":"redirect","required":false,"in":"query","description":"Where to send the caller afterwards.","schema":{"type":"string"},"example":"https://example.com/thanks"},{"name":"stepId","in":"path","required":true,"description":"Step to resume at.","schema":{"type":"string"},"example":"step-3"}],"requestBody":{"description":"Event payload.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"A result, or a redirect","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Connect"]},"get":{"operationId":"ConnectController_handleUnifiedAutomationWebhookGet","summary":"Automation webhook (GET)","description":"The GET form, for use as a link in an email — a recipient clicking it advances the automation and is then sent to `redirect`. Same unauthenticated, URL-as-credential caveat as the POST form.\n\n#### Signature\n\n```http\nGET /connect/automation/{id}/{stepId}/{entity}/{activity} (id: string, stepId: string, entity: string, activity: string, orgId?: string, automationId?: string, redirect?: string) -> A result, or a redirect\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the URL is the credential.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /connect/automation/{id}/{stepId}/{entity}/{activity}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"orgId","required":true,"in":"query","schema":{"type":"string"},"example":"org_4821"},{"name":"entity","required":true,"in":"path","description":"What the event concerns.","schema":{"type":"string"},"example":"customer"},{"name":"activity","required":true,"in":"path","description":"What happened.","schema":{"type":"string"},"example":"confirmed"},{"name":"id","required":true,"in":"path","description":"Automation id.","schema":{"type":"string"},"example":"AUT-4821"},{"name":"stepId","required":false,"in":"query","description":"Step ID","schema":{"type":"string"}},{"name":"automationId","required":false,"in":"query","schema":{"type":"string"},"example":"AUT-4821"},{"name":"redirect","required":false,"in":"query","schema":{"type":"string"},"example":"https://example.com/thanks"},{"name":"stepId","in":"path","required":true,"description":"Step to resume at.","schema":{"type":"string"},"example":"step-3"}],"responses":{"200":{"description":"A result, or a redirect","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Connect"]}},"/phone/numbers":{"get":{"operationId":"PhoneController_getPhoneNumbers","summary":"List phone numbers","description":"Every phone number configured for the organization, with its capabilities and purpose.\n\n#### Signature\n\n```http\nGET /phone/numbers () -> Phone numbers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/available`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Phone numbers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"post":{"operationId":"PhoneController_purchasePhoneNumber","summary":"Purchase a phone number","description":"Buys a number from the provider and adds it to the organization.\n\n**This incurs a real, recurring charge.** Numbers bill monthly until released, so provision deliberately rather than as a side effect of a setup flow.\n\n#### Signature\n\n```http\nPOST /phone/numbers (body) -> The purchased number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Recurring cost from the moment of purchase. Release numbers you stop using.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /phone/numbers/{phoneId}`\n- `POST /phone/numbers/add`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which number to buy.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"phoneNumber":"+14155552600","friendlyName":"Support line","purpose":"support"}}}},"responses":{"201":{"description":"The purchased number","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/numbers/{phoneId}":{"get":{"operationId":"PhoneController_getPhoneNumberById","summary":"Get a phone number","description":"Fetches one number with its routing, capabilities and SMS registration state.\n\n#### Signature\n\n```http\nGET /phone/numbers/{phoneId} (phoneId: string) -> The phone number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PHONE_NOT_FOUND | Phone number not found | No phone number in the org has that id. | List them with `GET /phone/numbers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/numbers/{phoneId}/sms-requirements`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":true,"in":"path","description":"Phone number id.","schema":{"type":"string"},"example":"PHN-4821"}],"responses":{"200":{"description":"The phone number","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Phone number not found — No phone number in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Phone number not found","path":"/phone/numbers/{phoneId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"put":{"operationId":"PhoneController_updatePhoneConfiguration","summary":"Update a phone number","description":"Updates a number's friendly name or purpose. Routing has its own endpoint.\n\n#### Signature\n\n```http\nPUT /phone/numbers/{phoneId} (phoneId: string, body) -> The updated number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PHONE_NOT_FOUND | Phone number not found | No phone number in the org has that id. | List them with `GET /phone/numbers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/numbers/{phoneId}/routing`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":true,"in":"path","description":"Phone number id.","schema":{"type":"string"},"example":"PHN-4821"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"friendlyName":"Sales line"}}}},"responses":{"200":{"description":"The updated number","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Phone number not found — No phone number in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Phone number not found","path":"/phone/numbers/{phoneId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"delete":{"operationId":"PhoneController_releasePhoneNumber","summary":"Release a phone number","description":"Releases a number back to the provider, ending the recurring charge.\n\n**Releasing is not reversible.** The number returns to the provider's pool and may be reassigned to someone else — anyone who calls or texts it afterwards reaches a stranger. Check nothing published still lists it.\n\n#### Signature\n\n```http\nDELETE /phone/numbers/{phoneId} (phoneId: string) -> Release result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- You cannot get the number back. Verify it is not in print, on a website, or in a customer record first.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PHONE_NOT_FOUND | Phone number not found | No phone number in the org has that id. | List them with `GET /phone/numbers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/numbers`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":true,"in":"path","description":"Phone number id.","schema":{"type":"string"},"example":"PHN-4821"}],"responses":{"200":{"description":"Release result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Phone number not found — No phone number in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Phone number not found","path":"/phone/numbers/{phoneId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/available":{"get":{"operationId":"PhoneController_getAvailableNumbers","summary":"Search available numbers","description":"Searches the provider for numbers available to buy. Listing is free and reserves nothing — a number shown here can be taken by someone else before you purchase it.\n\n#### Signature\n\n```http\nGET /phone/available (areaCode?: string, region?: string, contains?: string, smsEnabled?: boolean, voiceEnabled?: boolean, limit?: integer) -> Available numbers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Search results are not held. Purchase promptly or re-search.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/numbers`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"limit","required":false,"in":"query","description":"How many results.","schema":{"type":"integer"},"example":20},{"name":"voiceEnabled","required":false,"in":"query","description":"Only voice-capable numbers.","schema":{"type":"boolean"},"example":true},{"name":"smsEnabled","required":false,"in":"query","description":"Only SMS-capable numbers.","schema":{"type":"boolean"},"example":true},{"name":"contains","required":false,"in":"query","description":"Digits the number should contain.","schema":{"type":"string"},"example":"2600"},{"name":"region","required":false,"in":"query","description":"Region or state.","schema":{"type":"string"},"example":"CA"},{"name":"areaCode","required":false,"in":"query","description":"Area code to search within.","schema":{"type":"string"},"example":"415"},{"name":"countryCode","required":false,"in":"query","description":"Country code (default: US)","schema":{}}],"responses":{"200":{"description":"Available numbers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/numbers/add":{"post":{"operationId":"PhoneController_addExistingPhoneNumber","summary":"Add an existing number","description":"Registers a number you already own with the platform, rather than buying a new one. Use this when porting in or when the number was bought directly with the provider — it adds no cost of its own.\n\n#### Signature\n\n```http\nPOST /phone/numbers/add (body) -> The added number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- E.164 format is required — `+` and country code, no spaces or punctuation.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/numbers`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The number to add.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["phoneNumber"],"properties":{"phoneNumber":{"type":"string","description":"E.164 format.","example":"+14155552600"},"friendlyName":{"type":"string","example":"Support line"},"purpose":{"type":"string","example":"support"}}},"example":{"phoneNumber":"+14155552600","friendlyName":"Support line","purpose":"support"}}}},"responses":{"201":{"description":"The added number","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/numbers/{phoneId}/routing":{"post":{"operationId":"PhoneController_configurePhoneRouting","summary":"Configure number routing","description":"Sets where calls and messages to a number go — an IVR, a queue, a forwarding destination. Misrouting silently sends customers nowhere, so verify with a test call after changing it.\n\n#### Signature\n\n```http\nPOST /phone/numbers/{phoneId}/routing (phoneId: string, body) -> The updated routing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PHONE_NOT_FOUND | Phone number not found | No phone number in the org has that id. | List them with `GET /phone/numbers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/numbers/{phoneId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":true,"in":"path","description":"Phone number id.","schema":{"type":"string"},"example":"PHN-4821"}],"requestBody":{"description":"The routing configuration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"voice":{"type":"queue","target":"support"},"sms":{"type":"inbox"}}}}},"responses":{"201":{"description":"The updated routing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Phone number not found — No phone number in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Phone number not found","path":"/phone/numbers/{phoneId}/routing","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/numbers/sms-status/{phoneId}":{"get":{"operationId":"PhoneController_checkSmsRegistrationStatus","summary":"Get SMS registration status","description":"Where a number stands in A2P registration. Registration takes days and can be rejected, so check status rather than assuming a submitted registration is a working one.\n\n#### Signature\n\n```http\nGET /phone/numbers/sms-status/{phoneId} (phoneId: string) -> Registration status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PHONE_NOT_FOUND | Phone number not found | No phone number in the org has that id. | List them with `GET /phone/numbers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/a2p/brand`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":true,"in":"path","description":"Phone number id.","schema":{"type":"string"},"example":"PHN-4821"}],"responses":{"200":{"description":"Registration status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Phone number not found — No phone number in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Phone number not found","path":"/phone/numbers/sms-status/{phoneId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/numbers/register-sms/{phoneId}":{"post":{"operationId":"PhoneController_registerForSms","summary":"Register a number for SMS","description":"Submits a number for A2P messaging registration. Asynchronous and subject to carrier approval — poll the status endpoint rather than treating a `200` as registration.\n\n#### Signature\n\n```http\nPOST /phone/numbers/register-sms/{phoneId} (phoneId: string, body) -> The submission result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Submission is not approval. Carriers can reject a campaign days later.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PHONE_NOT_FOUND | Phone number not found | No phone number in the org has that id. | List them with `GET /phone/numbers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/numbers/sms-status/{phoneId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":true,"in":"path","description":"Phone number id.","schema":{"type":"string"},"example":"PHN-4821"}],"requestBody":{"description":"Registration details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"campaignUseCase":"customer_care"}}}},"responses":{"201":{"description":"The submission result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Phone number not found — No phone number in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Phone number not found","path":"/phone/numbers/register-sms/{phoneId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/a2p/site-pages":{"post":{"operationId":"PhoneController_publishA2PSitePages","summary":"Publish SMS terms and a privacy policy","description":"Publishes the SMS Terms and Privacy Policy pages carriers look for on the org's site, filled with its details. Pages the site already has are kept. Returns the page links and a suggested opt-in description.\n\n#### Signature\n\n```http\nPOST /phone/a2p/site-pages (body) -> { site, siteUrl, pages, suggestedMessageFlow }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"{ site, siteUrl, pages, suggestedMessageFlow }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"],"requestBody":{"description":"Optional business details to use.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/phone/calls/ai":{"post":{"operationId":"PhoneController_placeAICall","summary":"Make an AI phone call (call someone)","description":"Dials a person from the organization's number and connects its AI voice assistant, which talks to them — you do not speak on the call. Give `to` (the number to call; 10-digit US numbers are fine), `reason` (what the call is for — the voice assistant works from it) and optionally `greeting` (its opening line), `assistantId` (which voice assistant; default: the one that answers the calling number) and `from` (which of the organization's numbers to call from — by default your own assigned number, else the default number, else a free one, else the system phone). Use it to call a customer back, confirm an appointment, or follow up.\n\n#### Signature\n\n```http\nPOST /phone/calls/ai (body) -> { sid, status, to, from, assistantId } — the call is placed; the voice assistant takes over when they answer.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Premium-rate and international numbers are refused.\n- The call is billed to the organization like any call.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/calls`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"{ sid, status, to, from, assistantId } — the call is placed; the voice assistant takes over when they answer.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"],"requestBody":{"description":"Who to call and why.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"to":"+16823470647","reason":"Call back about their website setup question and offer to book a walkthrough.","greeting":"Hi, this is Appmint calling about your website question."}}}}}},"/phone/a2p/draft":{"get":{"operationId":"PhoneController_a2pDraft","summary":"The SMS registration, pre-filled","description":"The registration form filled from what the platform knows: the registered brand, business profile, org settings (address, email, phone), the signed-in user as representative, the website and its SMS pages, and this number's previous submission. Includes `platform.ready` — false until the platform's own Twilio profile is approved.\n\n#### Signature\n\n```http\nGET /phone/a2p/draft (phoneId?: string) -> The pre-filled submission\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/a2p/check`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":false,"in":"query","schema":{"type":"string"},"description":"The number being registered.","example":"6a8267a95850d7f4f4b92ae5"}],"responses":{"200":{"description":"The pre-filled submission","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/a2p/check":{"post":{"operationId":"PhoneController_checkA2P","summary":"Check an SMS registration before submitting","description":"Runs the checks that catch the usual rejection reasons. Returns { ok, problems: [{ field, severity: block|warn, message }] } — registration refuses a submission with block problems.\n\n#### Signature\n\n```http\nPOST /phone/a2p/check (body) -> { ok, problems, brandExists }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/numbers/register-sms/{phoneId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"{ ok, problems, brandExists }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"],"requestBody":{"description":"The submission.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/phone/a2p/brand":{"get":{"operationId":"PhoneController_getA2PBrand","summary":"Get the A2P brand","description":"The organization's A2P brand registration — the business identity carriers approve campaigns against. Numbers cannot be registered until the brand is.\n\n#### Signature\n\n```http\nGET /phone/a2p/brand () -> The A2P brand\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/numbers/register-sms/{phoneId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The A2P brand","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/numbers/{phoneId}/sms-requirements":{"get":{"operationId":"PhoneController_getSmsRegistrationRequirements","summary":"Get SMS requirements for a number","description":"What still has to be done before this number can send SMS reliably — brand registration, campaign approval, and the rest of A2P 10DLC.\n\nRead this before relying on a number for messaging. An unregistered number does not fail loudly; carriers simply filter its traffic.\n\n#### Signature\n\n```http\nGET /phone/numbers/{phoneId}/sms-requirements (phoneId: string) -> Outstanding requirements\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PHONE_NOT_FOUND | Phone number not found | No phone number in the org has that id. | List them with `GET /phone/numbers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/numbers/register-sms/{phoneId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":true,"in":"path","description":"Phone number id.","schema":{"type":"string"},"example":"PHN-4821"}],"responses":{"200":{"description":"Outstanding requirements","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Phone number not found — No phone number in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Phone number not found","path":"/phone/numbers/{phoneId}/sms-requirements","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/setup":{"post":{"operationId":"PhoneController_setupPhone","summary":"Set up phone","description":"Configures phone service for the organization — provider credentials and defaults. Run `GET /phone/verify` afterwards to confirm it works before depending on it.\n\n#### Signature\n\n```http\nPOST /phone/setup (body) -> The setup result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The body carries provider credentials — do not log it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/verify`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Setup details.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"provider":"twilio","accountSid":"AC…","authToken":"…"}}}},"responses":{"201":{"description":"The setup result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/verify":{"get":{"operationId":"PhoneController_verifySetup","summary":"Verify phone setup","description":"Checks the phone configuration is complete and the provider reachable. The first thing to run when calls or messages stop arriving.\n\n#### Signature\n\n```http\nGET /phone/verify () -> Verification result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/setup`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"phoneId","required":false,"in":"query","description":"Specific phone to verify (optional)","schema":{"type":"string"}}],"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/token":{"post":{"operationId":"PhoneController_generateVoiceToken","summary":"Get a phone access token","description":"Issues a short-lived token for a softphone or browser client to connect to the voice service. Tokens expire — fetch one per session rather than caching.\n\n#### Signature\n\n```http\nPOST /phone/token (body) -> The access token\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The token permits placing calls. Treat it as a credential.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/voice/register-device`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Token request.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"identity":"ada@example.com"}}}},"responses":{"200":{"description":"Voice access token","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"identity":{"type":"string"},"expiresIn":{"type":"number"}}}}}},"201":{"description":"The access token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/user-phones":{"get":{"operationId":"PhoneController_getUserPhones","summary":"Get user phone assignments","description":"Which numbers are assigned to which users — who receives calls to what.\n\n#### Signature\n\n```http\nGET /phone/user-phones () -> User phone assignments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/numbers`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"User phone assignments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/voice/app":{"get":{"operationId":"PhoneController_getVoiceApp","summary":"Get the voice application","description":"The voice application configuration — how inbound calls are handled before any per-number routing applies.\n\n#### Signature\n\n```http\nGET /phone/voice/app () -> The voice application\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/voice/app`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string","description":"Frontend-generated stable device id, persisted in localStorage (web) / Keychain (iOS) / EncryptedSharedPreferences (Android). Reuse the same value across page reloads / app relaunches. Required for correct multi-device behavior — multiple tabs/apps can register under the same user identity and all ring in parallel."},"platform":{"type":"string","enum":["web","ios","android"],"default":"web"},"label":{"type":"string","description":"Human-readable device label for admin UI (e.g., \"Chrome on MacBook\")"},"capabilities":{"type":"array","items":{"type":"string","enum":["voice","sms"]},"default":["voice"],"description":"What this device wants to receive. \"voice\" = incoming calls (Twilio Client). \"sms\" = inbound SMS for this user's assigned numbers (pushed via websocket). Pass [\"voice\", \"sms\"] for a full softphone."}}}}}},"responses":{"200":{"description":"The voice application","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"post":{"operationId":"PhoneController_createVoiceApp","summary":"Update the voice application","description":"Updates the voice application configuration. This affects call handling org-wide, so a mistake here takes out every number at once — test with a call afterwards.\n\n#### Signature\n\n```http\nPOST /phone/voice/app (body) -> The updated application\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Org-wide effect. Verify with a real call.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/voice/app`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The configuration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"greeting":"Thanks for calling Acme","fallbackNumber":"+14155552601"}}}},"responses":{"201":{"description":"The updated application","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/voice/voices":{"get":{"operationId":"PhoneController_getAiVoices","summary":"List available AI voices","description":"Every AI voice the org can put on a call, from every engine, as `[{ name, info, platform, previewUrl? }]`. `previewUrl` is a short sample to play before choosing: ElevenLabs' own hosted sample, or for OpenAI a sample rendered once and stored. A voice whose sample does not exist yet is returned without it (the render starts in the background) — the list never waits or fails on previews.\n\n#### Signature\n\n```http\nGET /phone/voice/voices () -> Available voices\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/voice/preview`\n- `POST /phone/voice/app`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Available voices","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/voice/preview":{"post":{"operationId":"PhoneController_previewVoice","summary":"Preview a line in an AI voice","description":"Renders a specific line — usually the assistant's greeting — in the chosen voice and returns a URL to the mp3. ElevenLabs voices render on ElevenLabs, OpenAI voices on OpenAI TTS; the caller does not care which.\n\nRendered once per (platform, voice, text) and stored, so previewing the same greeting again is instant and free. New lines are capped per org per hour.\n\n#### Signature\n\n```http\nPOST /phone/voice/preview (body) -> `{ url }` — a public mp3 URL\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- 400 with a plain message for an unknown platform, a voice that is not on that platform, or empty / over-300-character text.\n- 429 when the org has rendered too many new lines this hour; cached lines are never counted.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/voice/voices`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"`{ url }` — a public mp3 URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"],"requestBody":{"description":"The voice and the line.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["voice","platform","text"],"properties":{"voice":{"type":"string","description":"The voice `name` from GET /phone/voice/voices — an OpenAI voice name or an ElevenLabs voice_id.","example":"marin"},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"The voice's `platform` from the same list."},"text":{"type":"string","maxLength":300,"description":"The line to speak.","example":"Thanks for calling Acme, how can I help?"}}},"example":{"voice":"marin","platform":"openai-realtime","text":"Thanks for calling Acme, how can I help?"}}}}}},"/phone/voice/register-device":{"post":{"operationId":"PhoneController_registerVoiceDevice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The registered device","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"],"summary":"Register a voice device","description":"Registers a device to receive calls — a softphone, a mobile app. Until registered, calls routed to that user will not ring anywhere.\n\n#### Signature\n\n```http\nPOST /phone/voice/register-device (body) -> The registered device\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/voice/heartbeat`","requestBody":{"description":"The device to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"identity":"ada@example.com","deviceToken":"apns_…","platform":"ios"}}}}}},"/phone/voice/heartbeat":{"post":{"operationId":"PhoneController_heartbeatVoiceDevice","summary":"Send a device heartbeat","description":"Keeps a registered device marked as available. A device that stops sending heartbeats is treated as offline and calls route past it — which is what stops a dead app silently swallowing calls.\n\n#### Signature\n\n```http\nPOST /phone/voice/heartbeat (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/voice/devices`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The device.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"identity":"ada@example.com","deviceId":"DEV-4821"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/voice/unregister-device":{"post":{"operationId":"PhoneController_unregisterVoiceDevice","summary":"Unregister a voice device","description":"Removes a device so it stops receiving calls. Do this when someone changes phone, or their old handset keeps ringing for calls they should not get.\n\n#### Signature\n\n```http\nPOST /phone/voice/unregister-device (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/voice/register-device`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The device to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"identity":"ada@example.com","deviceId":"DEV-4821"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/voice/devices":{"get":{"operationId":"PhoneController_listVoiceDevices","summary":"List registered voice devices","description":"Devices currently registered to receive calls, and whether each is live. Where a \"why did nobody answer\" investigation starts.\n\n#### Signature\n\n```http\nGET /phone/voice/devices () -> Registered devices\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/voice/heartbeat`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Registered devices","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/system":{"get":{"operationId":"PhoneController_getSystemPhone","summary":"Get the system phone configuration","description":"The platform-level phone configuration, as opposed to the org's own numbers.\n\n#### Signature\n\n```http\nGET /phone/system () -> System phone configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/system`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"System phone configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"post":{"operationId":"PhoneController_setSystemPhone","summary":"Update the system phone configuration","description":"Updates the platform-level phone configuration.\n\n#### Signature\n\n```http\nPOST /phone/system (body) -> The updated configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /phone/system`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The configuration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"phoneNumber":"+14155552600"}}}},"responses":{"201":{"description":"The updated configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"delete":{"operationId":"PhoneController_clearSystemPhone","summary":"Remove the system phone configuration","description":"Clears the platform-level phone configuration. Anything relying on a system number stops working.\n\n#### Signature\n\n```http\nDELETE /phone/system () -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/system`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/sms-number":{"get":{"operationId":"PhoneController_getSmsPhone","summary":"Get the default SMS number","description":"The number outbound SMS is sent from by default.\n\n#### Signature\n\n```http\nGET /phone/sms-number () -> The default SMS number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/sms-number`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The default SMS number","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"post":{"operationId":"PhoneController_setSmsPhone","summary":"Set the default SMS number","description":"Sets the number outbound SMS is sent from.\n\nThe number is **checked for sendability first**: it must exist in the org and be capable of sending, and the refusal names the specific reason — usually incomplete A2P registration. That check is what stops the org silently defaulting to a number whose messages carriers drop.\n\n#### Signature\n\n```http\nPOST /phone/sms-number (body) -> The updated default\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PHONE_NUMBER_REQUIRED | phoneNumber is required | `phoneNumber` is missing. | Supply the number in E.164 format. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/numbers/{phoneId}/sms-requirements`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The number to use.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["phoneNumber"],"properties":{"phoneNumber":{"type":"string","description":"E.164 format.","example":"+14155552600"}}},"example":{"phoneNumber":"+14155552600"}}}},"responses":{"201":{"description":"The updated default","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"phoneNumber is required — `phoneNumber` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"phoneNumber is required","path":"/phone/sms-number","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]},"delete":{"operationId":"PhoneController_clearSmsPhone","summary":"Clear the default SMS number","description":"Removes the default SMS number. Outbound SMS with no explicit sender then has nowhere to send from.\n\n#### Signature\n\n```http\nDELETE /phone/sms-number () -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/sms-number`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/migration/status":{"get":{"operationId":"PhoneController_checkMigrationStatus","summary":"Get phone migration status","description":"Progress of a phone configuration migration.\n\n#### Signature\n\n```http\nGET /phone/migration/status () -> Migration status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /phone/migration/run`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Migration status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"]}},"/phone/migration/run":{"post":{"operationId":"PhoneController_runMigration","summary":"Run the phone migration","description":"Runs the phone configuration migration. An administrative operation that rewrites phone configuration — run it once, deliberately, and check the status afterwards.\n\n#### Signature\n\n```http\nPOST /phone/migration/run (body) -> The migration result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Rewrites configuration across the org's numbers.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /phone/migration/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The migration result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Phone"],"requestBody":{"description":"Optional options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/ai/voice/session":{"post":{"operationId":"VoiceTalkController_start","summary":"Start talking to the in-app assistant","description":"A short-lived token for the browser voice session, and the voice to speak in (the main assistant's voice, talking speed and greeting). Everything said goes to the same in-app agent as the chat.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Voice"]}},"/ai/voice/session/end":{"post":{"operationId":"VoiceTalkController_end","summary":"The voice session ended: body { sessionId, conversationId? }. Charges the minutes talked.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Voice"]}},"/ai/agent/setup":{"get":{"operationId":"AIAgentSetupController_status","summary":"The AI Agent's setup","description":"Whether its voice is on, its own record (open it in the assistant editor to change its voice and instructions), the agreement, and the price.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["AI Agent"]}},"/ai/agent/setup/enable":{"post":{"operationId":"AIAgentSetupController_enable","summary":"Turn on voice for the AI Agent","description":"Accepts the AI Agent agreement (recorded against the signed-in user) and gives the AI Agent its own record in AI Assistants. Deleting that record turns voice off and withdraws the agreement.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["AI Agent"]}},"/automation/start/{automationId}":{"post":{"operationId":"AutomationController_startAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Start an automation","description":"Arms the automation's trigger. From this point it runs **unattended** whenever the trigger fires — sending email, writing records, calling external services and, depending on its actions, moving money.\n\nValidate the definition and check the trigger's selectivity first: a trigger that matches more records than intended runs its actions on all of them.\n\n#### Signature\n\n```http\nPOST /automation/start/{automationId} (automationId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Arms unattended, repeating execution.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/ai/validate`\n- `POST /automation/stop/{automationId}`","tags":["Automation"],"requestBody":{"description":"Optional start options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/automation/stop/{automationId}":{"post":{"operationId":"AutomationController_stopAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Stop an automation","description":"Disarms the trigger so the automation stops firing. Executions already in flight run to completion — stopping is not a kill switch for work already started.\n\n#### Signature\n\n```http\nPOST /automation/stop/{automationId} (automationId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- In-flight executions still finish.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/start/{automationId}`","tags":["Automation"],"requestBody":{"description":"Optional stop options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/automation/status/{automationId}":{"get":{"operationId":"AutomationController_getAutomationStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"200":{"description":"The status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an automation's status","description":"Whether an automation is running, when it last fired, and its recent outcome.\n\n#### Signature\n\n```http\nGET /automation/status/{automationId} (automationId: string) -> The status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /automation/execution/history`","tags":["Automation"]}},"/automation/health":{"get":{"operationId":"AutomationController_getHealth","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Health","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get automation health","description":"Whether the automation engine is processing — the check to run when nothing seems to be firing.\n\n#### Signature\n\n```http\nGET /automation/health () -> Health\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /automation/dashboard/stats`","tags":["Automation"]}},"/automation/dashboard/stats":{"get":{"operationId":"AutomationController_getDashboardStats","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get automation statistics","description":"Run counts, success and failure rates across the org's automations.\n\n#### Signature\n\n```http\nGET /automation/dashboard/stats () -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /automation/execution/history`","tags":["Automation"]}},"/automation/execution/history":{"get":{"operationId":"AutomationController_getExecutionHistory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":false,"in":"query","schema":{"type":"string"},"example":"AUT-4821"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"example":0}],"responses":{"200":{"description":"Executions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get execution history","description":"Past automation runs and their outcomes — the record for working out why something did or did not happen.\n\n#### Signature\n\n```http\nGET /automation/execution/history (automationId?: string, limit?: integer, offset?: integer) -> Executions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /automation/dashboard/stats`","tags":["Automation"]}},"/automation":{"get":{"operationId":"AutomationController_getAutomations","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Automations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List automations","description":"The org's automations and whether each is running.\n\n#### Signature\n\n```http\nGET /automation () -> Automations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /automation/{automationId}`","tags":["Automation"]},"post":{"operationId":"AutomationController_createAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The automation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create an automation","description":"Defines an automation — its trigger, conditions and actions. **Created stopped**: it does nothing until started, which is the safe default for a definition that has not been reviewed.\n\n#### Signature\n\n```http\nPOST /automation (body) -> The automation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Created inactive.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/start/{automationId}`","tags":["Automation"],"requestBody":{"description":"The automation.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Welcome email","trigger":{"type":"record.created","datatype":"customer"},"conditions":[],"actions":[{"type":"send-email","template":"welcome"}]}}}}}},"/automation/executions/{executionId}":{"get":{"operationId":"AutomationController_getRun","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"executionId","required":true,"in":"path","schema":{"type":"string"},"description":"Run id (`exec_…`).","example":"exec_1759000000000_ab12cd"}],"responses":{"200":{"description":"The run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Run not found — No run with that executionId.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Run not found","path":"/automation/executions/{executionId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a run","description":"One run: its summary, step results, errors, completed steps and the variables it carried.\n\n#### Signature\n\n```http\nGET /automation/executions/{executionId} (executionId: string) -> The run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RUN_NOT_FOUND | Run not found | No run with that executionId. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Automation"]}},"/automation/executions/{executionId}/cancel":{"post":{"operationId":"AutomationController_cancelRun","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"executionId","required":true,"in":"path","schema":{"type":"string"},"description":"Run id (`exec_…`).","example":"exec_1759000000000_ab12cd"}],"responses":{"201":{"description":"{ cancelled: true, executionId }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This run has already finished — there is nothing to stop. — The run finished or failed (\"already failed\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This run has already finished — there is nothing to stop.","path":"/automation/executions/{executionId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Run not found — No run with that executionId.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Run not found","path":"/automation/executions/{executionId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Stop a run","description":"Cancels a run that is still going.\n\n#### Signature\n\n```http\nPOST /automation/executions/{executionId}/cancel (executionId: string) -> { cancelled: true, executionId }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RUN_NOT_FOUND | Run not found | No run with that executionId. | — |\n| `400` | ALREADY_DONE | This run has already finished — there is nothing to stop. | The run finished or failed (\"already failed\"). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Automation"]}},"/automation/executions/{executionId}/retry":{"post":{"operationId":"AutomationController_retryRun","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"executionId","required":true,"in":"path","schema":{"type":"string"},"description":"Run id (`exec_…`).","example":"exec_1759000000000_ab12cd"}],"responses":{"201":{"description":"{ success, executionId, retryOf, message }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Run not found — No run with that executionId.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Run not found","path":"/automation/executions/{executionId}/retry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Run it again","description":"Runs the automation again now with the same variables, as a new run linked to this one (`retryOf`). Real effects, like any run.\n\n#### Signature\n\n```http\nPOST /automation/executions/{executionId}/retry (executionId: string) -> { success, executionId, retryOf, message }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RUN_NOT_FOUND | Run not found | No run with that executionId. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Automation"]}},"/automation/{automationId}/runs":{"get":{"operationId":"AutomationController_runsLog","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"stepId","in":"query","required":false,"schema":{"type":"string"}},{"name":"outcome","in":"query","required":false,"schema":{"type":"string","enum":["succeeded","failed"]}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1}},{"name":"pageSize","in":"query","required":false,"description":"Max 200.","schema":{"type":"integer","default":25}}],"responses":{"200":{"description":"{ total, page, pageSize, pages, data }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Run log","description":"Runs of this automation, newest first, paged. `status` takes a comma-separated list; `stepId` with `outcome` (succeeded | failed) keeps runs where that step had that outcome; `q` searches the runs; `from`/`to` bound the start time.\n\n#### Signature\n\n```http\nGET /automation/{automationId}/runs (automationId: string, status?: string, stepId?: string, outcome?: string, q?: string, from?: string, to?: string, page?: integer, pageSize?: integer) -> { total, page, pageSize, pages, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Automation"]}},"/automation/{automationId}/steps/{stepId}/outcomes":{"get":{"operationId":"AutomationController_stepOutcomes","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"},{"name":"stepId","required":true,"in":"path","schema":{"type":"string"},"description":"Step id in the workflow."},{"name":"outcome","in":"query","required":false,"schema":{"type":"string","enum":["succeeded","failed"]}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"{ step, totals: { reached, succeeded, failed }, total, page, pageSize, pages, data }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Automation not found — No automation with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Automation not found","path":"/automation/{automationId}/steps/{stepId}/outcomes","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"One step across runs","description":"For every run that reached this step, newest first: its outcome, duration, what it produced or the error, and where the flow went next. `outcome` narrows to succeeded or failed.\n\n#### Signature\n\n```http\nGET /automation/{automationId}/steps/{stepId}/outcomes (automationId: string, stepId: string, outcome?: string, q?: string, page?: integer, pageSize?: integer) -> { step, totals: { reached, succeeded, failed }, total, page, pageSize, pages, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AUTOMATION_NOT_FOUND | Automation not found | No automation with that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Automation"]}},"/automation/{automationId}/monitor":{"get":{"operationId":"AutomationController_monitor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"},{"name":"days","required":false,"in":"query","schema":{"type":"integer","default":14}}],"responses":{"200":{"description":"{ automation, … }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Automation not found — No automation with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Automation not found","path":"/automation/{automationId}/monitor","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Monitor an automation","description":"The monitoring page in one call for the last `days` (default 14): the automation and whether its trigger is registered (re-registered first if it had dropped), run counts and rates, runs in flight, per-step figures (ran, failed, average ms, last error) and errors grouped by message.\n\n#### Signature\n\n```http\nGET /automation/{automationId}/monitor (automationId: string, days?: integer) -> { automation, … }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AUTOMATION_NOT_FOUND | Automation not found | No automation with that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Automation"]}},"/automation/{automationId}/check":{"get":{"operationId":"AutomationController_checkAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"200":{"description":"{ ready, problems }","content":{"application/json":{"schema":{"type":"object","properties":{"ready":{"type":"boolean"},"problems":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Automation not found — No automation with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Automation not found","path":"/automation/{automationId}/check","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Check an automation before running it","description":"What stands between this workflow and running, step by step, so problems show before anyone presses Start. Nothing is run.\n\n#### Signature\n\n```http\nGET /automation/{automationId}/check (automationId: string) -> { ready, problems }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AUTOMATION_NOT_FOUND | Automation not found | No automation with that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Automation"]}},"/automation/{automationId}":{"get":{"operationId":"AutomationController_getAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"200":{"description":"The automation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an automation","description":"One automation with its trigger, conditions and actions.\n\n#### Signature\n\n```http\nGET /automation/{automationId} (automationId: string) -> The automation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /automation/{automationId}`","tags":["Automation"]},"put":{"operationId":"AutomationController_updateAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"200":{"description":"The updated automation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update an automation","description":"Changes an automation's definition. Editing a **running** automation changes what fires from the next trigger onward — stop it first if the edit is not one you want taking effect mid-way.\n\n#### Signature\n\n```http\nPUT /automation/{automationId} (automationId: string, body) -> The updated automation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Takes effect immediately on a running automation.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/stop/{automationId}`","tags":["Automation"],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"actions":[{"type":"send-email","template":"welcome-v2"}]}}}}},"delete":{"operationId":"AutomationController_deleteAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete an automation","description":"Removes an automation and its execution history. Stop it rather than delete it if the history matters — the record of what it did goes with it.\n\n#### Signature\n\n```http\nDELETE /automation/{automationId} (automationId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Discards execution history.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/stop/{automationId}`","tags":["Automation"],"requestBody":{"description":"Optional options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/automation/{automationId}/execute":{"post":{"operationId":"AutomationController_executeAutomation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"automationId","required":true,"in":"path","schema":{"type":"string"},"description":"Automation id.","example":"AUT-4821"}],"responses":{"201":{"description":"The execution result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Execute an automation now","description":"Runs an automation immediately, outside its trigger. **Not a dry run** — the actions happen for real, including anything that sends or charges.\n\nUse it to test with a deliberately harmless payload, or to re-run a case that should have fired and did not.\n\n#### Signature\n\n```http\nPOST /automation/{automationId}/execute (automationId: string, body) -> The execution result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Real side effects — not a simulation.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/ai/validate`","tags":["Automation"],"requestBody":{"description":"The payload to run against.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"record":{"id":"CUST-4821","email":"ada@example.com"}}}}}}},"/automation/ai/generate":{"post":{"operationId":"AutomationAiController_generateAutomation","summary":"Generate an automation from a description","description":"Drafts an automation definition from plain language. The output is a **draft**: it is created stopped and should be read before being armed, because a plausible-looking trigger can match far more than intended.\n\nConsumes AI credit.\n\n#### Signature\n\n```http\nPOST /automation/ai/generate (body) -> The drafted automation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Review before starting. Consumes AI credit.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/ai/validate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What the automation should do.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"prompt":"When a new customer signs up, wait a day, then email them a getting-started guide."}}}},"responses":{"200":{"description":"Returns the generated automation workflow JSON","content":{"application/json":{"schema":{"type":"object","properties":{"workflow":{"type":"object","description":"Generated automation workflow"},"explanation":{"type":"string","description":"Human-readable explanation of the generated automation"},"confidence":{"type":"number","description":"AI confidence score (0-1)"}}}}}},"201":{"description":"The drafted automation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Automation"]}},"/automation/ai/improve":{"post":{"operationId":"AutomationAiController_improveAutomation","summary":"Suggest automation improvements","description":"Reviews an existing automation and suggests changes. Suggestions only — nothing is applied.\n\n#### Signature\n\n```http\nPOST /automation/ai/improve (body) -> Suggestions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only. Consumes AI credit.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /automation/{automationId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The automation to review.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"automationId":"AUT-4821"}}}},"responses":{"201":{"description":"Suggestions","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Automation"]}},"/automation/ai/validate":{"post":{"operationId":"AutomationAiController_validateAutomation","summary":"Validate an automation","description":"Checks an automation definition for problems before it is armed — missing fields, unreachable conditions, actions that will fail. The cheap check to run between drafting and starting.\n\n#### Signature\n\n```http\nPOST /automation/ai/validate (body) -> The validation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/start/{automationId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The definition to check.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"trigger":{"type":"record.created","datatype":"customer"},"actions":[{"type":"send-email","template":"welcome"}]}}}},"responses":{"201":{"description":"The validation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Automation"]}},"/automation/ai/templates":{"post":{"operationId":"AutomationAiController_getAutomationTemplates","summary":"Get automation templates","description":"Suggested starting points for a described goal — templates to adapt rather than write from scratch.\n\n#### Signature\n\n```http\nPOST /automation/ai/templates (body) -> Templates\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /automation/ai/generate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Templates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Automation"],"requestBody":{"description":"What you are trying to automate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"goal":"follow up on abandoned carts"}}}}}},"/enrichment/search/persons":{"post":{"operationId":"DataEnrichmentController_searchPersons","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Matching people","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search for people","description":"Searches provider data for people matching criteria. Each search calls an external provider and typically consumes credit with them.\n\n#### Signature\n\n```http\nPOST /enrichment/search/persons (body) -> Matching people\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Calls a paid third-party provider.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /enrichment/person`","tags":["Data enrichment"],"requestBody":{"description":"Search criteria.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Ada Lovelace","company":"Acme"}}}}}},"/enrichment/search/companies":{"post":{"operationId":"DataEnrichmentController_searchCompanies","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Matching companies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search for companies","description":"Searches provider data for companies matching criteria.\n\n#### Signature\n\n```http\nPOST /enrichment/search/companies (body) -> Matching companies\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Calls a paid third-party provider.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /enrichment/company`","tags":["Data enrichment"],"requestBody":{"description":"Search criteria.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Acme","domain":"acme.example"}}}}}},"/enrichment/person":{"post":{"operationId":"DataEnrichmentController_enrichPerson","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The enriched profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Enrich a person","description":"Looks up additional data for one person from an identifier — usually an email address. The data comes from a third party and can be stale or wrong; treat it as a hint, not a fact about a real individual.\n\n#### Signature\n\n```http\nPOST /enrichment/person (body) -> The enriched profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Third-party data — accuracy is not guaranteed.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /enrichment/bulk`","tags":["Data enrichment"],"requestBody":{"description":"Who to enrich.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}}}},"/enrichment/company":{"post":{"operationId":"DataEnrichmentController_enrichCompany","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The enriched company","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Enrich a company","description":"Looks up additional data for a company, usually from its domain.\n\n#### Signature\n\n```http\nPOST /enrichment/company (body) -> The enriched company\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Third-party data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /enrichment/person`","tags":["Data enrichment"],"requestBody":{"description":"Which company.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"acme.example"}}}}}},"/enrichment/validate-email":{"post":{"operationId":"DataEnrichmentController_validateEmail","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Validate an email address","description":"Checks an address for deliverability through the provider. Worth running before adding an address to a send list — invalid addresses cause bounces, which damage sender reputation.\n\n#### Signature\n\n```http\nPOST /enrichment/validate-email (body) -> Validation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/validate/email/{email}`","tags":["Data enrichment"],"requestBody":{"description":"The address.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}}}},"/enrichment/bulk":{"post":{"operationId":"DataEnrichmentController_bulkEnrich","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Per-record results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Bulk enrich","description":"Enriches many records in one call. Each record is a provider lookup, so cost and duration scale with the list — check the provider's credit before submitting a large batch.\n\n#### Signature\n\n```http\nPOST /enrichment/bulk (body) -> Per-record results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Cost scales with the batch size.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /enrichment/person`","tags":["Data enrichment"],"requestBody":{"description":"The records to enrich.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"type":"person","items":[{"email":"ada@example.com"},{"email":"grace@example.com"}]}}}}}},"/enrichment/providers":{"get":{"operationId":"DataEnrichmentController_getProviders","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Providers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List enrichment providers","description":"The third-party data providers available for enrichment.\n\n#### Signature\n\n```http\nGET /enrichment/providers () -> Providers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /enrichment/providers/{name}/status`","tags":["Data enrichment"]}},"/enrichment/providers/{name}/status":{"get":{"operationId":"DataEnrichmentController_getProviderStatus","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Provider name.","example":"clearbit"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Provider status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a provider's status","description":"Whether a provider is reachable and configured — the check when enrichment starts returning nothing.\n\n#### Signature\n\n```http\nGET /enrichment/providers/{name}/status (name: string) -> Provider status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /enrichment/providers`","tags":["Data enrichment"]}},"/enrichment/cache/clear":{"post":{"operationId":"DataEnrichmentController_clearCache","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Clear the enrichment cache","description":"Drops cached provider responses. The next lookups go back to the provider and are charged again — clear it when data is known stale, not as routine housekeeping.\n\n#### Signature\n\n```http\nPOST /enrichment/cache/clear () -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Subsequent lookups are re-charged.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /enrichment/person`","tags":["Data enrichment"]}},"/storefront/data/{siteId}":{"get":{"operationId":"StorefrontController_data","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"siteId","required":true,"in":"path","schema":{"type":"string"},"description":"Site `sk` or `name`. Identifies which site configuration (filters, currencies) to bundle.","example":"main-store"},{"name":"brand","in":"query","required":false,"description":"Brand name, matched case-insensitively. Repeat for several brands.","schema":{"type":"string"},"example":"fizzco"},{"name":"collection","in":"query","required":false,"description":"Restrict the `collectionDTO` bundle to this collection name.","schema":{"type":"string"}},{"name":"categories","in":"query","required":false,"description":"Comma-separated category names. Matched case-insensitively against `post.categories`.","schema":{"type":"string"},"example":"cold-drinks,featured"},{"name":"category","in":"query","required":false,"description":"Single category name. Alias for a one-value `categories`.","schema":{"type":"string"},"example":"cold-drinks"},{"name":"tags","in":"query","required":false,"description":"Comma-separated tags, matched case-insensitively against `post.tags`.","schema":{"type":"string"},"example":"summer,sale"},{"name":"tag","in":"query","required":false,"description":"Single tag. Alias for a one-value `tags`.","schema":{"type":"string"}},{"name":"minPrice","in":"query","required":false,"description":"Lower price bound, inclusive.","schema":{"type":"number"},"example":5},{"name":"maxPrice","in":"query","required":false,"description":"Upper price bound, inclusive.","schema":{"type":"number"},"example":50},{"name":"<attribute>","in":"query","required":false,"description":"Any query parameter that is not a reserved name is treated as a product attribute facet, comma-separated for multiple values — e.g. `?size=330ml,500ml&color=red`. Matched against `data.attributes[].options[].value`.","schema":{"type":"string"},"example":"330ml,500ml"},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"l","in":"query","required":false,"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth.","schema":{"type":"string"}},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"}],"responses":{"200":{"description":"The full landing-page bundle","content":{"application/json":{"schema":{"type":"object","properties":{"brandDTO":{"type":"object","description":"Brands, paged.","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}},"collectionDTO":{"type":"object","description":"Collections, paged.","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}},"filters":{"type":"object","additionalProperties":true,"description":"Facet configuration from the site record (`data.storefront.filters`)."},"productDTO":{"type":"object","description":"The product page matching the supplied filters.","properties":{"data":{"type":"array","items":{"type":"object","description":"A storefront product (`sf_product`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"cola-330ml"},"title":{"type":"string","example":"Cola 330ml"},"price":{"type":"number","example":12},"brand":{"type":"string","example":"fizzco"},"hide":{"type":"boolean","description":"When true the product is excluded from every storefront listing.","example":false},"attributes":{"type":"array","description":"Faceted attributes, each with selectable options.","items":{"type":"object","properties":{"name":{"type":"string","example":"size"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","example":"330ml"}}}}}}},"calculatedPrice":{"type":"object","description":"Price resolved for the calling customer at read time — tier discounts, promotions and rules already applied. Never cache this across customers.","properties":{"originalPrice":{"type":"number","description":"List price before any discount.","example":12},"finalPrice":{"type":"number","description":"Price the customer actually pays.","example":9.6},"discount":{"type":"number","description":"Absolute amount discounted.","example":2.4},"discountPercent":{"type":"number","description":"Discount as a percentage of `originalPrice`.","example":20},"appliedRule":{"type":"object","additionalProperties":true,"description":"The pricing rule that won, if any."},"appliedDiscounts":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Every discount that contributed."},"freeShipping":{"type":"boolean","description":"Whether the winning rule grants free shipping.","example":false}}}}}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}},"currencies":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Currencies enabled on the site."},"minMaxPrice":{"type":"object","nullable":true,"description":"Catalog price bounds, for a price-range slider. `null` when the catalog is empty.","properties":{"minPrice":{"type":"number","example":1.5},"maxPrice":{"type":"number","example":499}}},"categories":{"type":"array","items":{"type":"object","description":"A node in the storefront category tree.","properties":{"name":{"type":"string","description":"Slug, unique across the entire tree. This is the value products carry in `post.categories`.","example":"cold-drinks"},"title":{"type":"string","description":"Display title.","example":"Cold Drinks"},"description":{"type":"string","description":"Category description.","example":"Chilled sodas, juices and water"},"image":{"type":"object","description":"Platform file reference.","properties":{"url":{"type":"string","description":"Publicly resolvable URL.","example":"https://cdn.appmint.io/acme/cold-drinks.png"},"path":{"type":"string","description":"Storage path within the org bucket."},"contentType":{"type":"string","example":"image/png"},"size":{"type":"integer","description":"Bytes."},"meta":{"type":"object","additionalProperties":true,"description":"Arbitrary metadata carried with the file."}}},"children":{"type":"array","description":"Nested child categories. Empty for a leaf.","items":{"type":"object","description":"Recursive category node."}}}},"description":"The storefront category tree."},"featuredProductDTO":{"type":"object","description":"Up to 10 products in the `featured` category.","properties":{"data":{"type":"array","items":{"type":"object","description":"A storefront product (`sf_product`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"cola-330ml"},"title":{"type":"string","example":"Cola 330ml"},"price":{"type":"number","example":12},"brand":{"type":"string","example":"fizzco"},"hide":{"type":"boolean","description":"When true the product is excluded from every storefront listing.","example":false},"attributes":{"type":"array","description":"Faceted attributes, each with selectable options.","items":{"type":"object","properties":{"name":{"type":"string","example":"size"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","example":"330ml"}}}}}}},"calculatedPrice":{"type":"object","description":"Price resolved for the calling customer at read time — tier discounts, promotions and rules already applied. Never cache this across customers.","properties":{"originalPrice":{"type":"number","description":"List price before any discount.","example":12},"finalPrice":{"type":"number","description":"Price the customer actually pays.","example":9.6},"discount":{"type":"number","description":"Absolute amount discounted.","example":2.4},"discountPercent":{"type":"number","description":"Discount as a percentage of `originalPrice`.","example":20},"appliedRule":{"type":"object","additionalProperties":true,"description":"The pricing rule that won, if any."},"appliedDiscounts":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Every discount that contributed."},"freeShipping":{"type":"boolean","description":"Whether the winning rule grants free shipping.","example":false}}}}}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}}}}}}},"404":{"description":"Site 'main-store' not found — `siteId` matches no site record in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Site 'main-store' not found","path":"/storefront/data/{siteId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"],"summary":"Get everything a storefront landing page needs","description":"One round trip that returns the full payload for a storefront home page: site configuration, brands, collections, a product page, the catalog price bounds, the category tree and a featured-products bundle.\n\nUse this instead of calling `/brands`, `/collections`, `/products` and `/categories` separately — it is a single query pass on the server and avoids four round trips from the browser.\n\nEvery list-shaped query parameter (`brand`, `collection`, paging, sorting) and every product filter is forwarded to the underlying list calls, so the same filtering vocabulary as `GET /storefront/products` applies.\n\n#### Signature\n\n```http\nGET /storefront/data/{siteId} (siteId: string, brand?: string, collection?: string, categories?: string, category?: string, tags?: string, tag?: string, brand?: string, minPrice?: number, maxPrice?: number, <attribute>?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string) -> The full landing-page bundle\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- `featuredProductDTO` is always the first 10 products in the `featured` category and ignores your filters.\n- Products with `data.hide: true` are excluded from every bundle in this response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SITE_NOT_FOUND | Site 'main-store' not found | `siteId` matches no site record in the org. | Check the site `name` or `sk`. Sites are listed under the site management API. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/products`\n- `GET /storefront/categories`"}},"/storefront/brands/{brand}":{"get":{"operationId":"StorefrontController_brands","summary":"List brands","description":"Returns the brands (`sf_brand`) defined for the org, paged. Supply the `brand` path segment to fetch one brand by its exact `name`; omit it to list all of them.\n\n#### Signature\n\n```http\nGET /storefront/brands/{brand} (brand: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string) -> A page of brand records\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Matching on `brand` is exact, not a prefix or substring search.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/products`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"l","required":false,"in":"query","description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth.","schema":{"type":"string"}},{"name":"st","required":false,"in":"query","description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"},{"name":"s","required":false,"in":"query","description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"ps","required":false,"in":"query","description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"p","required":false,"in":"query","description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"brand","required":true,"in":"path","description":"Exact brand `name`. Omit the segment entirely to list every brand.","schema":{"type":"string"},"example":"fizzco"}],"responses":{"200":{"description":"A page of brand records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A brand (`sf_brand`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}},"example":{"data":[{"id":"66f1a2b3c4d5e6f708192a3b","datatype":"sf_brand","name":"fizzco","title":"FizzCo","data":{"name":"fizzco","title":"FizzCo","logo":null}}],"total":1,"page":1,"pageSize":50}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"]}},"/storefront/attributes/{attribute}":{"get":{"operationId":"StorefrontController_attributes","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"attribute","in":"path","required":true,"description":"Exact attribute `name`.","schema":{"type":"string"},"example":"size"}],"responses":{"200":{"description":"A page of attribute records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A filter attribute (`sf_attribute`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"],"summary":"List storefront filter attributes","description":"Returns the faceted filter attributes (`sf_attribute`) used to build storefront filter UI, sorted by `filterPosition` ascending. Supply `attribute` to fetch one by exact `name`.\n\n#### Signature\n\n```http\nGET /storefront/attributes/{attribute} (attribute: string) -> A page of attribute records\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- **Legacy behaviour, verified in `StorefrontCatalogService.getAttributes`:** when the `attribute` segment is omitted this endpoint returns **brands** (`sf_brand`), not attributes. To list all attributes you must currently read them through the repository API. Treat the no-argument form as unreliable and do not build against it.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/brands/{brand}`"}},"/storefront/collections/{collection}":{"get":{"operationId":"StorefrontController_collections","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"l","required":false,"in":"query","schema":{"type":"string"},"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth."},{"name":"collection","in":"path","required":true,"description":"Exact collection `name`.","schema":{"type":"string"},"example":"summer-2026"},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans. Defaults to 100 here.","schema":{"type":"integer","default":100},"example":50},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"}],"responses":{"200":{"description":"A page of collection records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A collection (`sf_collection`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"],"summary":"List collections","description":"Returns merchandising collections (`sf_collection`), paged. Supply `collection` to fetch one by exact `name`.\n\n#### Signature\n\n```http\nGET /storefront/collections/{collection} (collection: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string) -> A page of collection records\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`."}},"/storefront/products":{"get":{"operationId":"StorefrontController_products","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"categories","in":"query","required":false,"description":"Comma-separated category names. Matched case-insensitively against `post.categories`.","schema":{"type":"string"},"example":"cold-drinks,featured"},{"name":"category","in":"query","required":false,"description":"Single category name. Alias for a one-value `categories`.","schema":{"type":"string"},"example":"cold-drinks"},{"name":"tags","in":"query","required":false,"description":"Comma-separated tags, matched case-insensitively against `post.tags`.","schema":{"type":"string"},"example":"summer,sale"},{"name":"tag","in":"query","required":false,"description":"Single tag. Alias for a one-value `tags`.","schema":{"type":"string"}},{"name":"brand","in":"query","required":false,"description":"Brand name, matched case-insensitively. Repeat for several brands.","schema":{"type":"string"},"example":"fizzco"},{"name":"minPrice","in":"query","required":false,"description":"Lower price bound, inclusive.","schema":{"type":"number"},"example":5},{"name":"maxPrice","in":"query","required":false,"description":"Upper price bound, inclusive.","schema":{"type":"number"},"example":50},{"name":"<attribute>","in":"query","required":false,"description":"Any query parameter that is not a reserved name is treated as a product attribute facet, comma-separated for multiple values — e.g. `?size=330ml,500ml&color=red`. Matched against `data.attributes[].options[].value`.","schema":{"type":"string"},"example":"330ml,500ml"},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"l","in":"query","required":false,"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth.","schema":{"type":"string"}},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"modifydate"},"example":"modifydate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"},{"name":"random","in":"query","required":false,"description":"Return results in random order instead of by `s`/`st`.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"A page of products, each with calculated pricing, plus catalog price bounds","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A storefront product (`sf_product`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"cola-330ml"},"title":{"type":"string","example":"Cola 330ml"},"price":{"type":"number","example":12},"brand":{"type":"string","example":"fizzco"},"hide":{"type":"boolean","description":"When true the product is excluded from every storefront listing.","example":false},"attributes":{"type":"array","description":"Faceted attributes, each with selectable options.","items":{"type":"object","properties":{"name":{"type":"string","example":"size"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","example":"330ml"}}}}}}},"calculatedPrice":{"type":"object","description":"Price resolved for the calling customer at read time — tier discounts, promotions and rules already applied. Never cache this across customers.","properties":{"originalPrice":{"type":"number","description":"List price before any discount.","example":12},"finalPrice":{"type":"number","description":"Price the customer actually pays.","example":9.6},"discount":{"type":"number","description":"Absolute amount discounted.","example":2.4},"discountPercent":{"type":"number","description":"Discount as a percentage of `originalPrice`.","example":20},"appliedRule":{"type":"object","additionalProperties":true,"description":"The pricing rule that won, if any."},"appliedDiscounts":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Every discount that contributed."},"freeShipping":{"type":"boolean","description":"Whether the winning rule grants free shipping.","example":false}}}}}}},"description":"The page of records."},"total":{"type":"integer","description":"Total matching records, ignoring pagination."},"page":{"type":"integer","description":"Page number echoed back."},"pageSize":{"type":"integer","description":"Page size echoed back."}}},{"type":"object","properties":{"minMaxPrice":{"type":"object","nullable":true,"description":"Catalog price bounds, for a price-range slider. `null` when the catalog is empty.","properties":{"minPrice":{"type":"number","example":1.5},"maxPrice":{"type":"number","example":499}}}}}]},"example":{"data":[{"id":"66f1a2b3c4d5e6f708192a3b","datatype":"sf_product","name":"cola-330ml","title":"Cola 330ml","data":{"sku":"DRK-COLA-330","title":"Cola 330ml","price":12,"brand":"fizzco","hide":false,"calculatedPrice":{"originalPrice":12,"finalPrice":9.6,"discount":2.4,"discountPercent":20,"appliedRule":{"name":"summer-sale"},"appliedDiscounts":[{"name":"summer-sale","amount":2.4}],"freeShipping":false}}}],"total":1,"page":1,"pageSize":50,"minMaxPrice":{"minPrice":1.5,"maxPrice":499}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"],"summary":"List and search products","description":"The main catalog query. Filters on categories, tags, brand, price range and arbitrary product attributes, then enriches every result with `data.calculatedPrice` resolved for the calling customer.\n\n**Attribute filtering is open-ended.** Any query parameter that is not one of the reserved names (`categories`, `category`, `tags`, `tag`, `brand`, `price`, `minPrice`, `maxPrice`, `sort`, `sortType`, `random`, `page`, `pageSize`, `lastItem`, `sort_by`, and the short forms `p`, `ps`, `s`, `st`, `l`) is treated as a product attribute facet. So `?size=330ml,500ml&color=red` filters on the `size` and `color` attributes with no server configuration.\n\nText matching on categories, tags and brands is **case-insensitive and exact per value** — `cold-drinks` matches `Cold-Drinks` but not `cold-drinks-large`.\n\n#### Signature\n\n```http\nGET /storefront/products (categories?: string, category?: string, tags?: string, tag?: string, brand?: string, minPrice?: number, maxPrice?: number, <attribute>?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string, random?: boolean) -> A page of products, each with calculated pricing, plus catalog price bounds\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Products with `data.hide: true` are never returned. Products without the field are returned.\n- `calculatedPrice` depends on who is calling — an anonymous request and a signed-in customer can see different `finalPrice` values for the same product. Do not cache the response across customers.\n- If pricing fails for an individual product, that product is returned without `calculatedPrice` rather than failing the whole request. Clients should fall back to `data.price`.\n- Prefer the `l` cursor over large `p` values: deep offset paging degrades on big catalogs.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/product/{id}`\n- `GET /storefront/data/{siteId}`"}},"/storefront/product/{id}":{"get":{"operationId":"StorefrontController_productBySlug","summary":"Get a product","description":"Fetches one product and enriches it with `data.calculatedPrice` for the calling customer at quantity 1.\n\nThe `id` segment is flexible: the record `sk`, the product `name` (its URL slug) or the `sku` all resolve, so a storefront can route `/p/cola-330ml` straight through without a lookup table.\n\n#### Signature\n\n```http\nGET /storefront/product/{id} (id: string) -> The product with calculated pricing, or `null` when `id` is empty\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A product that does not exist resolves to `null` with a `200`, not a `404`. Check for a null body.\n- Pricing is computed at quantity 1. Quantity-tiered prices are resolved at cart time, not here.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/products`\n- `GET /storefront/product/{id}/related`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Product `sk`, `name` (slug) or `sku`.","schema":{"type":"string"},"example":"cola-330ml"}],"responses":{"200":{"description":"The product with calculated pricing, or `null` when `id` is empty","content":{"application/json":{"schema":{"type":"object","description":"A storefront product (`sf_product`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"cola-330ml"},"title":{"type":"string","example":"Cola 330ml"},"price":{"type":"number","example":12},"brand":{"type":"string","example":"fizzco"},"hide":{"type":"boolean","description":"When true the product is excluded from every storefront listing.","example":false},"attributes":{"type":"array","description":"Faceted attributes, each with selectable options.","items":{"type":"object","properties":{"name":{"type":"string","example":"size"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","example":"330ml"}}}}}}},"calculatedPrice":{"type":"object","description":"Price resolved for the calling customer at read time — tier discounts, promotions and rules already applied. Never cache this across customers.","properties":{"originalPrice":{"type":"number","description":"List price before any discount.","example":12},"finalPrice":{"type":"number","description":"Price the customer actually pays.","example":9.6},"discount":{"type":"number","description":"Absolute amount discounted.","example":2.4},"discountPercent":{"type":"number","description":"Discount as a percentage of `originalPrice`.","example":20},"appliedRule":{"type":"object","additionalProperties":true,"description":"The pricing rule that won, if any."},"appliedDiscounts":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Every discount that contributed."},"freeShipping":{"type":"boolean","description":"Whether the winning rule grants free shipping.","example":false}}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"]}},"/storefront/product/{id}/related":{"get":{"operationId":"StorefrontController_productRelated","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","in":"path","required":true,"description":"Product SKU.","schema":{"type":"string"},"example":"DRK-COLA-330"}],"responses":{"200":{"description":""},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"An unexpected error occurred. Our team has been notified. — Always — the handler is a stub that throws `Method not implemented.`","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"An unexpected error occurred. Our team has been notified.","path":"/storefront/product/{id}/related","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront"],"summary":"Get related products","description":"> **Deprecated.** Not implemented. `StorefrontCatalogService.getProductRelated` throws unconditionally, so this endpoint always returns `500`. Do not integrate against it; it is documented only so its behaviour is not a surprise.\n\nIntended to return products related to the given one.\n\n#### Signature\n\n```http\nGET /storefront/product/{id}/related (id: string)\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | NOT_IMPLEMENTED | An unexpected error occurred. Our team has been notified. | Always — the handler is a stub that throws `Method not implemented.` | Use `GET /storefront/products` with a shared category or tag to build a related-items rail instead. |\n\nPlus the standard platform errors: `429`.\n\n#### See also\n\n- `GET /storefront/products`","deprecated":true}},"/storefront/categories":{"get":{"operationId":"StorefrontController_categories","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The root category's children, or `[]` when the tree is empty","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A node in the storefront category tree.","properties":{"name":{"type":"string","description":"Slug, unique across the entire tree. This is the value products carry in `post.categories`.","example":"cold-drinks"},"title":{"type":"string","description":"Display title.","example":"Cold Drinks"},"description":{"type":"string","description":"Category description.","example":"Chilled sodas, juices and water"},"image":{"type":"object","description":"Platform file reference.","properties":{"url":{"type":"string","description":"Publicly resolvable URL.","example":"https://cdn.appmint.io/acme/cold-drinks.png"},"path":{"type":"string","description":"Storage path within the org bucket."},"contentType":{"type":"string","example":"image/png"},"size":{"type":"integer","description":"Bytes."},"meta":{"type":"object","additionalProperties":true,"description":"Arbitrary metadata carried with the file."}}},"children":{"type":"array","description":"Nested child categories. Empty for a leaf.","items":{"type":"object","description":"Recursive category node."}}}}},"example":[{"name":"drinks","title":"Drinks","description":"Everything to drink","children":[{"name":"cold-drinks","title":"Cold Drinks","description":"Chilled sodas, juices and water","children":[]}]}]}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"],"summary":"Get the storefront category tree","description":"Returns the storefront category tree as a nested array.\n\nThe tree is not one record per category — it lives entirely in the `children` array of a single `category` record named `storefront`. The `category` collection is shared across domains (CRM, content, storefront), each with its own root, which is why every storefront category call resolves that root first.\n\nThe root is created on demand, so a brand-new org gets `[]` rather than an error.\n\n#### Signature\n\n```http\nGET /storefront/categories () -> The root category's children, or `[]` when the tree is empty\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The root `storefront` node itself is never returned — only its children.\n- Calling this on a fresh org has a side effect: it creates the root `category` record.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/categories`"},"post":{"operationId":"StorefrontController_addCategory","summary":"Add a storefront category","description":"Adds a category to the storefront tree. It always lands under the root `storefront` category, which is created automatically if the org does not have one yet. Pass `parent` (an existing category name) to nest it one level deeper instead of at the top level.\n\n**Idempotent.** Posting a name that already exists *anywhere* in the tree returns that node unchanged, with a `201`, and writes nothing. Retrying a request whose response you lost is therefore safe.\n\n**Names are slugified and globally unique.** `name` (or `title` when `name` is absent) is lowercased, non-alphanumeric runs collapse to `-`, leading and trailing dashes are trimmed and the result is cut to 50 characters — so `\"Cold Drinks\"` is stored as `cold-drinks`. Uniqueness is enforced across the whole tree, not just among siblings, because `name` is the only thing a product carries in `post.categories`; two nodes sharing a name would each list the other's products.\n\n`parent` is matched by name anywhere in the tree, so you can nest under a category without knowing its path.\n\n#### Signature\n\n```http\nPOST /storefront/categories (body) -> The added (or already-existing) node, plus the full tree\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- The write is a partial update on `data.children` only, so the root record's other fields — and every other root sharing the `category` collection — are untouched.\n- There is no update or delete endpoint for storefront categories; edit the `category` record named `storefront` through the repository API.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CATEGORY_NAME_REQUIRED | Category name is required | Neither `name` nor `title` is supplied, or the value slugifies to an empty string (e.g. `\"!!!\"`). | Send a `name` containing at least one alphanumeric character. |\n| `404` | PARENT_NOT_FOUND | Parent category 'drinks' not found | `parent` is set but no category with that name exists anywhere in the tree. | Create the parent first, or call `GET /storefront/categories` to see the available names. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/categories`\n- `GET /storefront/products`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The category to add. Same shape as the `category` model, plus an optional `parent`.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":50,"description":"Slugified before storing (`^[a-zA-Z_\\-0-9]*$`). This is the value products carry in `post.categories`, and it must be unique across the whole storefront tree.","example":"Cold Drinks"},"title":{"type":"string","description":"Display title. Defaults to `name` as given.","example":"Cold Drinks"},"description":{"type":"string","description":"Category description.","example":"Chilled sodas, juices and water"},"image":{"type":"object","description":"Platform file reference.","properties":{"url":{"type":"string","description":"Publicly resolvable URL.","example":"https://cdn.appmint.io/acme/cold-drinks.png"},"path":{"type":"string","description":"Storage path within the org bucket."},"contentType":{"type":"string","example":"image/png"},"size":{"type":"integer","description":"Bytes."},"meta":{"type":"object","additionalProperties":true,"description":"Arbitrary metadata carried with the file."}}},"parent":{"type":"string","description":"Name of an existing category to nest under, matched anywhere in the tree. Omit for a top-level category.","example":"drinks"}}},"examples":{"topLevel":{"summary":"Top-level category","description":"Lands directly under the storefront root.","value":{"name":"Drinks","description":"Everything to drink"}},"nested":{"summary":"Nested under an existing category","description":"`parent` is matched by name anywhere in the tree.","value":{"name":"Cold Drinks","title":"Cold Drinks","description":"Chilled sodas, juices and water","parent":"drinks"}},"withImage":{"summary":"With a category image","description":"`image` takes the platform FileInfo shape and is stored verbatim.","value":{"name":"Cold Drinks","parent":"drinks","image":{"url":"https://cdn.appmint.io/acme/cold-drinks.png","path":"acme/cold-drinks.png","contentType":"image/png"}}}}}}},"responses":{"201":{"description":"The added (or already-existing) node, plus the full tree","content":{"application/json":{"schema":{"type":"object","properties":{"category":{"type":"object","description":"The node as stored. On a repeat call, the pre-existing node.","properties":{"name":{"type":"string","description":"Slug, unique across the entire tree. This is the value products carry in `post.categories`.","example":"cold-drinks"},"title":{"type":"string","description":"Display title.","example":"Cold Drinks"},"description":{"type":"string","description":"Category description.","example":"Chilled sodas, juices and water"},"image":{"type":"object","description":"Platform file reference.","properties":{"url":{"type":"string","description":"Publicly resolvable URL.","example":"https://cdn.appmint.io/acme/cold-drinks.png"},"path":{"type":"string","description":"Storage path within the org bucket."},"contentType":{"type":"string","example":"image/png"},"size":{"type":"integer","description":"Bytes."},"meta":{"type":"object","additionalProperties":true,"description":"Arbitrary metadata carried with the file."}}},"children":{"type":"array","description":"Nested child categories. Empty for a leaf.","items":{"type":"object","description":"Recursive category node."}}}},"categories":{"type":"array","items":{"type":"object","description":"A node in the storefront category tree.","properties":{"name":{"type":"string","description":"Slug, unique across the entire tree. This is the value products carry in `post.categories`.","example":"cold-drinks"},"title":{"type":"string","description":"Display title.","example":"Cold Drinks"},"description":{"type":"string","description":"Category description.","example":"Chilled sodas, juices and water"},"image":{"type":"object","description":"Platform file reference.","properties":{"url":{"type":"string","description":"Publicly resolvable URL.","example":"https://cdn.appmint.io/acme/cold-drinks.png"},"path":{"type":"string","description":"Storage path within the org bucket."},"contentType":{"type":"string","example":"image/png"},"size":{"type":"integer","description":"Bytes."},"meta":{"type":"object","additionalProperties":true,"description":"Arbitrary metadata carried with the file."}}},"children":{"type":"array","description":"Nested child categories. Empty for a leaf.","items":{"type":"object","description":"Recursive category node."}}}},"description":"The complete storefront tree after the write."}}},"example":{"category":{"name":"cold-drinks","title":"Cold Drinks","description":"Chilled sodas, juices and water","children":[]},"categories":[{"name":"drinks","title":"Drinks","description":"","children":[{"name":"cold-drinks","title":"Cold Drinks","description":"Chilled sodas, juices and water","children":[]}]}]}}}},"400":{"description":"Category name is required — Neither `name` nor `title` is supplied, or the value slugifies to an empty string (e.g. `\"!!!\"`).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Category name is required","path":"/storefront/categories","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Parent category 'drinks' not found — `parent` is set but no category with that name exists anywhere in the tree.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Parent category 'drinks' not found","path":"/storefront/categories","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"],"x-codeSamples":[{"lang":"bash","label":"cURL","source":"curl -X POST https://api.appmint.io/storefront/categories \\\n  -H 'orgid: acme-retail' \\\n  -H 'Authorization: Bearer <jwt>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{ \"name\": \"Cold Drinks\", \"parent\": \"drinks\" }'"},{"lang":"javascript","label":"fetch","source":"const res = await fetch('https://api.appmint.io/storefront/categories', {\n  method: 'POST',\n  headers: {\n    orgid: 'acme-retail',\n    Authorization: `Bearer ${token}`,\n    'Content-Type': 'application/json',\n  },\n  body: JSON.stringify({ name: 'Cold Drinks', parent: 'drinks' }),\n});\n\nconst { category, categories } = await res.json();"}]}},"/storefront/pos-categories":{"get":{"operationId":"StorefrontController_posCategories","summary":"Get POS quick-pick categories","description":"The top-of-screen quick category buckets used by the in-store POS (Apps / Mains / Drinks / …).\n\nThese are deliberately *not* the storefront category tree: they are sourced from the `posCategory` entry in the `sf_attribute` collection so operators can edit the POS layout in one place without touching the catalog taxonomy.\n\n#### Signature\n\n```http\nGET /storefront/pos-categories () -> The option list, or `[]` when `posCategory` has not been seeded\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- An empty array means the `posCategory` attribute has not been configured yet, not that there was an error.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/categories`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The option list, or `[]` when `posCategory` has not been seeded","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","description":"Button caption.","example":"Drinks"},"value":{"type":"string","description":"Category value the POS filters products by.","example":"drinks"},"param":{"type":"string","description":"Optional extra parameter carried with the selection."}}}},"example":[{"label":"Apps","value":"appetisers"},{"label":"Mains","value":"mains"},{"label":"Drinks","value":"drinks"}]}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront"]}},"/storefront/pos/tab":{"post":{"operationId":"StorefrontController_openPosTab","summary":"Open a POS tab","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Tab details. All fields optional.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"businessLocationId":{"type":"string","description":"Location the tab belongs to. Also scopes auto-alias numbering.","example":"loc_downtown"},"alias":{"type":"string","description":"Operator-facing label. Auto-generated as `Walk-up #N` when omitted.","example":"Table 7"},"tabLabel":{"type":"string","description":"Deprecated alias for `alias`, accepted for older clients. Prefer `alias`.","deprecated":true},"servicePointId":{"type":"string","description":"Table or counter to seat the tab at immediately.","example":"sp_t7"},"customer":{"type":"object","description":"Contact to attach. Also populates the order-level `email`, `phone` and `name`, which receipts fall back to.","properties":{"sk":{"type":"string","description":"Existing customer record."},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}},"examples":{"walkUp":{"summary":"Walk-up tab","description":"Empty body — the server names it `Walk-up #N`.","value":{}},"table":{"summary":"Named table at a location","value":{"businessLocationId":"loc_downtown","alias":"Table 7","servicePointId":"sp_t7"}},"withCustomer":{"summary":"Tab with a contact","description":"Attaching contact details up front lets `send-receipt` work without a recipient.","value":{"businessLocationId":"loc_downtown","alias":"Smith party","customer":{"name":"Ada Lovelace","email":"ada@example.com"}}}}}}},"responses":{"201":{"description":"The created tab","content":{"application/json":{"schema":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/storefront/pos/tab","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/storefront/pos/tab","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't use the register: <requirement titles>. — A requirement rule whose `enforcement.pos` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"pos","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"],"description":"Opens a new tab and returns it with `status: \"new\"`. Every field is optional — an empty body is a valid walk-up tab.\n\nWhen `alias` is omitted the server auto-numbers one from the tabs currently open at that location, giving `Walk-up #1`, `Walk-up #2` and so on. Because the count is taken at open time, aliases are not reserved: two tabs opened simultaneously at one location can receive the same auto-alias. Pass an explicit `alias` where that matters.\n\nA tab is an `sf_order` and lives in the same collection as web orders with the same shape — a unique `number`, `productItems[]`, and the monetary fields — so reporting and integrations see one consistent model regardless of channel.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab (body) -> The created tab\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Auto-generated aliases are not unique under concurrency. Send an explicit `alias` when you need a guaranteed one.\n- The tab opens empty; add lines with `POST /storefront/order/{id}/items`.\n- POS gate (whole register) for the signed-in operator: 423 `readiness_block`; a manager resends with `override`, stamped on the tab as `readinessOverride`. Customers and non-staff callers are never gated.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `423` | READINESS_BLOCK | <name> can't use the register: <requirement titles>. | A requirement rule whose `enforcement.pos` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/pos/tabs`\n- `POST /storefront/order/{id}/items`\n- `POST /storefront/pos/tab/{id}/settle`"}},"/storefront/pos/readiness":{"get":{"operationId":"StorefrontController_posOperatorReadiness","summary":"May the signed-in operator use the POS?","description":"The Workforce Readiness `pos` gate, asked when the POS screen opens. Never throws for a block — it describes it, so the screen can explain and offer the override. Pass `permissionScope` (e.g. `alcohol`) to ask about one gated permission. The same gate is enforced server-side on POST /storefront/pos/tab and POST /storefront/order/{id}/items.\n\n#### Signature\n\n```http\nGET /storefront/pos/readiness (businessLocationId?: string, permissionScope?: string) -> `{ allowed, staff, permissionScope, effect, employeeId, employeeName, blocking[], warnings[], reasons[], overrideActive }`; when not allowed, also the standard block fields (`reason: \"readiness_block\"`, `message`, `canOverride`, `override`). A caller who isn’t staff gets `{ allowed: true, staff: false }`.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab`\n- `POST /storefront/order/{id}/items`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"The register’s location.","example":"harbor-grill"},{"name":"permissionScope","required":false,"in":"query","schema":{"type":"string"},"description":"One POS permission to check, e.g. alcohol. Empty = the whole register.","example":"alcohol"}],"responses":{"200":{"description":"`{ allowed, staff, permissionScope, effect, employeeId, employeeName, blocking[], warnings[], reasons[], overrideActive }`; when not allowed, also the standard block fields (`reason: \"readiness_block\"`, `message`, `canOverride`, `override`). A caller who isn’t staff gets `{ allowed: true, staff: false }`.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"allowed":false,"staff":true,"permissionScope":"alcohol","reason":"readiness_block","code":"READINESS_BLOCK","gate":"pos","message":"Luis Ramirez can't sell alcohol items: Alcohol server cert.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tabs":{"get":{"operationId":"StorefrontController_listPosTabs","summary":"List open POS tabs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one location. Omit to list every location in the org.","example":"loc_downtown"},{"name":"unpaid","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Open tabs, newest first","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"],"description":"Returns the tabs an operator might still act on, newest first, with line items task-enriched.\n\n\"Open\" is defined by exclusion: any tab whose status is **not** one of `completed`, `cancelled`, `returned`, `refunded`, `failed` is returned. That deliberately includes **fully-paid tabs** — a paid tab stays on the floor until the operator closes it out — and it makes the list resilient to unexpected status values rather than hiding them.\n\n#### Signature\n\n```http\nGET /storefront/pos/tabs (businessLocationId?: string) -> Open tabs, newest first\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Capped at 200 tabs. There is no pagination — a location with more than 200 open tabs is truncated.\n- Paid-but-not-closed tabs appear here, not in the closed list. Check `amountPaid` against `amount` to tell them apart.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/pos/tabs/closed`\n- `POST /storefront/pos/tab`"}},"/storefront/pos/tabs/closed":{"get":{"operationId":"StorefrontController_listClosedPosTabs","summary":"List closed POS tabs","description":"The exact complement of the open list: tabs whose status **is** one of `completed`, `cancelled`, `returned`, `refunded`, `failed`, newest first, with line items task-enriched. Intended for audit and reporting views (\"show me yesterday's tabs\").\n\n#### Signature\n\n```http\nGET /storefront/pos/tabs/closed (businessLocationId?: string) -> Closed tabs, newest first\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Capped at 200 tabs with no pagination or date filter, so this is not a full historical export. Query `sf_order` through the repository API for reporting over long ranges.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/pos/tabs`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one location.","example":"loc_downtown"}],"responses":{"200":{"description":"Closed tabs, newest first","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/order/{id}":{"get":{"operationId":"StorefrontController_getOrderById","summary":"Get an order with tasks enriched","description":"Returns one order or tab by `sk`, with `productItems[].task` populated for every line that has been fired. This is the read to use when rendering a tab, because it resolves workflow state in the same round trip.\n\n#### Signature\n\n```http\nGET /storefront/order/{id} (id: string) -> The order with fired lines task-enriched\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/order/{id}/payments`\n- `POST /storefront/order/{id}/fire`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Order `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The order with fired lines task-enriched","content":{"application/json":{"schema":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/order/{id}/payments":{"get":{"operationId":"StorefrontController_getOrderPayments","summary":"List payments and balance for an order","description":"The authoritative payment view for an order. Matches `sf_transaction.invoiceNumber` case-insensitively against the order `sk`, display number and name, then returns every linked transaction together with computed `totalPaid`, `balance` and `paymentStatus`.\n\nUse this rather than reconstructing payment state on the client — the matching rules and the refund arithmetic live on the server, and client-side heuristics have historically disagreed with the ledger.\n\n#### Signature\n\n```http\nGET /storefront/order/{id}/payments (id: string) -> Linked transactions plus computed totals\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/settle`\n- `POST /storefront/pos/tab/{id}/refund`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Order `sk`, display number, or name.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"Linked transactions plus computed totals","content":{"application/json":{"schema":{"type":"object","properties":{"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Matching `sf_transaction` records, refunds included as negative amounts."},"totalPaid":{"type":"number","description":"Net of refunds.","example":25.92},"balance":{"type":"number","description":"Order total minus `totalPaid`. Zero or negative means settled.","example":0},"paymentStatus":{"type":"string","description":"Derived status, e.g. `paid`, `paid-partial`, `unpaid`.","example":"paid"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/settle":{"post":{"operationId":"StorefrontController_settlePosTab","summary":"Settle a POS tab","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Open POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"description":"The tender to record.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Tender amount in the order currency. Must be greater than zero.","example":25.92},"method":{"type":"string","description":"How it was paid — `card`, `cash`, `giftcard`, …. Recorded for reporting.","example":"card"},"gateway":{"type":"string","default":"manual","description":"Processor. `stripe` verifies with the gateway before recording; anything else records offline.","example":"stripe"},"ref":{"type":"string","description":"Gateway or operator reference, stored on the payment line.","example":"ch_3PabcXYZ"},"tip":{"type":"number","description":"Tip included in `amount`.","example":3},"currency":{"type":"string","description":"Defaults to the order currency, then `USD`.","example":"USD"},"tendered":{"type":"number","description":"Cash only: the note handed over for this tender; change = tendered − (amount + tip).","example":50}}},"examples":{"cash":{"summary":"Cash, paid in full","value":{"amount":25.92,"method":"cash"}},"cashWithChange":{"summary":"Cash share with change","value":{"amount":18.5,"tip":2,"method":"cash","tendered":50}},"cardWithTip":{"summary":"Card via Stripe with a tip","description":"`tip` is part of `amount`, not additional to it.","value":{"amount":28.92,"tip":3,"method":"card","gateway":"stripe","ref":"ch_3PabcXYZ"}},"partial":{"summary":"Partial payment","description":"Leaves the tab `paid-partial`; call again for the balance.","value":{"amount":10,"method":"cash"}}}}}},"responses":{"201":{"description":"The updated tab and the recorded transaction","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"transaction":{"type":"object","additionalProperties":true,"description":"The `sf_transaction` written for this tender."},"accounting":{"type":"object","nullable":true,"additionalProperties":true,"description":"Ledger posting result once fully paid: status complete | needs_attention, issues[]."},"stock":{"type":"object","nullable":true,"additionalProperties":true,"description":"Stock deduction result once fully paid: status complete | needs_attention, issues[]."}}}}}},"400":{"description":"Order is already paid in full — `amountPaid` already covers the order total.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Order is already paid in full","path":"/storefront/pos/tab/{id}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This sale has a recorded cash refund. Use a new tab for another sale; retry refund records separately. — The tab already has a POS refund recorded.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This sale has a recorded cash refund. Use a new tab for another sale; retry refund records separately.","path":"/storefront/pos/tab/{id}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"],"description":"Records a tender against the tab, increments `amountPaid`, appends to `payments[]` and recomputes the status to `paid` or `paid-partial`.\n\nAn `sf_transaction` is **always** written, including for cash, so accounting and reporting see every tender. When `gateway` is `stripe` the transaction is verified with Stripe before it is recorded; every other gateway is recorded as paid offline on trust.\n\nPartial settlement is supported — call it repeatedly until the balance reaches zero. Each call is a new tender, so this endpoint is **not idempotent**: a retried request records a second payment.\n\n**Overpayment**: only a cash tender may exceed what is due — the payment is recorded as the amount due and `tendered`/change are kept on the payment line. Any other tender for more than is due is refused. For a cash share of a split, send `tendered` (the note handed over) and the change is worked out against `amount + tip`.\n\n**Gift cards** are paid by redeeming them first (`POST /storefront/giftcards/redeem`) and settling with `gateway: \"giftcard\"` and the GCT reference; a gift-card tender recorded any other way is refused.\n\nWhen the tab becomes fully paid the sale is posted to the ledger and stock is deducted. Those results come back as `accounting` and `stock`; if either could not complete it is saved as `needs_attention` for `retry-accounting` / `retry-stock` — never a reason to charge again.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/settle (id: string, body) -> The updated tab and the recorded transaction\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Not idempotent — a retry records an additional tender. Confirm with the payments endpoint before retrying a request whose response you lost.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | ALREADY_PAID | Order is already paid in full | `amountPaid` already covers the order total. | Read `GET /storefront/order/{id}/payments` to confirm the balance before settling. |\n| `409` | REFUNDED_SALE | This sale has a recorded cash refund. Use a new tab for another sale; retry refund records separately. | The tab already has a POS refund recorded. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/order/{id}/payments`\n- `POST /storefront/pos/tab/{id}/refund`"}},"/storefront/pos/tab/{id}/breakdown":{"get":{"operationId":"StorefrontController_posTabBreakdown","summary":"Get the money breakdown of a POS tab","description":"Read-only, server-priced figures for the settle screen: the recorded subtotal, discount, tax and total; what is paid and the tips collected; `balanceDue`; tip options (15/18/20/25% of subtotal) and the chosen tip; per-person split amounts; and, for the tenders the cashier is laying out, their total, what is still `outstanding`, the \"pay rest\" fill for each, the charge each will make (the tip rides the first) and cash `change`. The client renders these numbers and never derives money itself.\n\n`selected` (line ids) splits the lines into `selectedTotal` / `unselectedTotal` for pay-by-item.\n\n#### Signature\n\n```http\nGET /storefront/pos/tab/{id}/breakdown (id: string, tip?: number, tipPercent?: number, split?: integer, pending?: string, tendered?: string, selected?: string) -> { order, breakdown }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Anonymous: StorefrontController is `@PublicRoute()` at class level and this handler does not override it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/settle`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"tip","required":false,"in":"query","schema":{"type":"number"},"description":"Tip amount. Ignored when `tipPercent` is given.","example":5},{"name":"tipPercent","required":false,"in":"query","schema":{"type":"number"},"description":"Tip as a percent of the subtotal.","example":18},{"name":"split","required":false,"in":"query","schema":{"type":"integer"},"description":"Split the balance evenly this many ways (2 or more).","example":3},{"name":"pending","required":false,"in":"query","schema":{"type":"string"},"description":"Comma list of tender amounts being laid out.","example":"20,15.5"},{"name":"tendered","required":false,"in":"query","schema":{"type":"string"},"description":"Comma list, parallel to `pending`: cash handed over for each, for change.","example":"20,20"},{"name":"selected","required":false,"in":"query","schema":{"type":"string"},"description":"Comma list of line ids for pay-by-item."}],"responses":{"200":{"description":"{ order, breakdown }","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"breakdown":{"type":"object","properties":{"subtotal":{"type":"number"},"discount":{"type":"number"},"tax":{"type":"number"},"taxRate":{"type":"number"},"taxName":{"type":"string"},"total":{"type":"number"},"paid":{"type":"number"},"tipsCollected":{"type":"number"},"collected":{"type":"number"},"balanceDue":{"type":"number"},"paidInFull":{"type":"boolean"},"tipOptions":{"type":"array","items":{"type":"object","properties":{"percent":{"type":"number"},"amount":{"type":"number"}}}},"tip":{"type":"number"},"dueNow":{"type":"number","description":"balanceDue + tip."},"splitAmounts":{"type":"array","nullable":true,"items":{"type":"number"},"description":"Even split of the balance; the last share absorbs rounding. Null unless `split` ≥ 2."},"splitOptions":{"type":"array","items":{"type":"object","properties":{"parts":{"type":"integer"},"each":{"type":"number"}}}},"pendingTotal":{"type":"number"},"outstanding":{"type":"number"},"tenders":{"type":"array","items":{"type":"object","properties":{"amount":{"type":"number"},"charge":{"type":"number"},"tipIncluded":{"type":"number"},"payRest":{"type":"number"},"change":{"type":"number"}}}},"selectedTotal":{"type":"number"},"unselectedTotal":{"type":"number"},"payments":{"type":"array","items":{"type":"object","properties":{"transactionId":{"type":"string"},"amount":{"type":"number"},"tip":{"type":"number"},"refundedAmount":{"type":"number"},"refunded":{"type":"boolean"},"refundable":{"type":"number"},"tendered":{"type":"number"},"change":{"type":"number"},"isRefund":{"type":"boolean"}}}}}}}}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/breakdown","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/close":{"post":{"operationId":"StorefrontController_closePosTab","summary":"Close a paid POS tab","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The completed order and the freed service point","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"servicePoint":{"type":"object","nullable":true,"additionalProperties":true}}}}}},"400":{"description":"Tab is already <status> — The tab is completed, cancelled, refunded or returned.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Tab is already <status>","path":"/storefront/pos/tab/{id}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"<n> sent items are still open at the station (<names>). Close the tab once the ticket is done. — A sent item's station task is not done.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"<n> sent items are still open at the station (<names>). Close the tab once the ticket is done.","path":"/storefront/pos/tab/{id}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"],"description":"Completes a fully paid tab (the order is completed in person) and frees its service point: the table goes to `dirty` — or `available` with `markDirty: false` — and its current tab, party size, server and seated time are cleared.\n\nRefused while anything is still owed, or while a sent item is still open at a station.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/close (id: string, body) -> The completed order and the freed service point\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Anonymous: StorefrontController is `@PublicRoute()` at class level and this handler does not override it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | TAB_CLOSED | Tab is already <status> | The tab is completed, cancelled, refunded or returned. | — |\n| `409` | ITEMS_OPEN | <n> sent items are still open at the station (<names>). Close the tab once the ticket is done. | A sent item's station task is not done. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/settle`\n- `POST /storefront/pos/tab/{id}/release-service-point`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"markDirty":{"type":"boolean","default":true,"description":"false leaves the table available instead of dirty."}}}}}}}},"/storefront/pos/tab/{id}/retry-accounting":{"post":{"operationId":"StorefrontController_retryPosAccounting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"POS tab `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The order and its accounting status","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"accounting":{"type":"object","additionalProperties":true,"description":"status complete | needs_attention, issues[], checkedAt."}}}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/retry-accounting","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This historical tab has no saved accounting snapshot; no payment or journal was changed — A tab settled before accounting snapshots existed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This historical tab has no saved accounting snapshot; no payment or journal was changed","path":"/storefront/pos/tab/{id}/retry-accounting","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"],"summary":"Retry the journal for a settled POS tab","description":"When settling a tab could not post its sale and payment journals (the tab's `posAccounting.status` is `needs_attention`), this replays the saved accounting snapshot. Idempotent: journals already posted are not posted again. No payment is changed.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/retry-accounting (id: string) -> The order and its accounting status\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A failure while posting is recorded in `accounting.issues` with `status: needs_attention`, not raised.\n- Anonymous: StorefrontController is `@PublicRoute()` at class level and this handler does not override it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `409` | NO_SNAPSHOT | This historical tab has no saved accounting snapshot; no payment or journal was changed | A tab settled before accounting snapshots existed. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/retry-stock`"}},"/storefront/pos/tab/{id}/retry-stock":{"post":{"operationId":"StorefrontController_retryPosStock","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"POS tab `sk`.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The order and its stock status","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"stock":{"type":"object","additionalProperties":true,"description":"status complete | needs_attention, issues[]."}}}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/retry-stock","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This order has no saved POS stock intent; historical sales are not deducted automatically — The tab has no saved stock intent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This order has no saved POS stock intent; historical sales are not deducted automatically","path":"/storefront/pos/tab/{id}/retry-stock","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"],"summary":"Retry the stock deduction for a settled POS tab","description":"When settling a fully paid tab could not deduct stock (the saved stock intent is `needs_attention`), this replays it and posts the cost of goods. Already complete intents are returned unchanged. Refused if the sold items or location changed after payment, or the sale was refunded.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/retry-stock (id: string) -> The order and its stock status\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Problems resolving items, the location or posting COGS are recorded in `stock.issues` rather than raised.\n- Anonymous: StorefrontController is `@PublicRoute()` at class level and this handler does not override it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `409` | NO_STOCK_INTENT | This order has no saved POS stock intent; historical sales are not deducted automatically | The tab has no saved stock intent. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/retry-accounting`"}},"/storefront/pos/tab/{id}/print-check":{"post":{"operationId":"StorefrontController_printPosCheck","summary":"Print a pre-payment check","description":"Builds an ESC/POS check from the tab and dispatches it to the location's configured printer via the hub-agent — the restaurant flow where a customer asks for the check before paying.\n\nThe printer is read from `location.data.pos.checkPrinter`, falling back to `receiptPrinter`. Use `GET /storefront/pos/tab/{id}/check-payload` instead when the client drives its own printer.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/print-check (id: string) -> Dispatch result from the hub-agent\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Requires a configured printer and a reachable hub-agent at the location; neither is validated before dispatch.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/pos/tab/{id}/check-payload`\n- `POST /storefront/pos/tab/{id}/print-receipt`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Open POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"Dispatch result from the hub-agent","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/print-check","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/print-receipt":{"post":{"operationId":"StorefrontController_printPosReceipt","summary":"Print or reprint a receipt","description":"Prints the receipt for a tab through the location's printer. Works in any payment state — the tender section is taken from the most recent entry in `payments[]` — so it serves both the moment of sale and a later reprint of an old receipt.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/print-receipt (id: string, copy?: string) -> Dispatch result from the hub-agent\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/pos/tab/{id}/receipt-payload`\n- `POST /storefront/pos/tab/{id}/send-receipt`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"copy","required":false,"in":"query","schema":{"type":"string","enum":["customer","merchant"]},"description":"Selects the matching copy template. Omit to let the resolver use whichever copy the operator configured.","example":"customer"}],"responses":{"201":{"description":"Dispatch result from the hub-agent","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/print-receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/receipt-payload":{"get":{"operationId":"StorefrontController_buildPosReceiptPayload","summary":"Get receipt print primitives","description":"Runs the same template resolution and render pipeline as `print-receipt` — org setting, then factory default; Handlebars-style interpolation; auto-skip-empty; loop, two-column and divider primitives — but returns the resolved lines instead of dispatching them.\n\nThis is the endpoint for clients that own their printer: a mobile app over Bluetooth, a kiosk over USB.\n\n#### Signature\n\n```http\nGET /storefront/pos/tab/{id}/receipt-payload (id: string, copy?: string) -> The resolved print primitives\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/print-receipt`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"copy","required":false,"in":"query","schema":{"type":"string","enum":["customer","merchant"]},"description":"Selects the copy variant template.","example":"customer"}],"responses":{"200":{"description":"The resolved print primitives","content":{"application/json":{"schema":{"type":"array","description":"Resolved print lines, in order.","items":{"type":"object","properties":{"kind":{"type":"string","description":"Primitive type — `text`, `twocol`, `divider`, `loop`, `qr`, `barcode`.","example":"twocol"},"text":{"type":"string","example":"Cola 330ml"},"bold":{"type":"boolean","example":false},"size":{"type":"string","description":"Relative size hint.","example":"normal"},"lines":{"type":"array","items":{"type":"string"},"description":"Column values for `twocol`."},"url":{"type":"string","description":"Payload for `qr` / `barcode`."}}}}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/receipt-payload","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/check-payload":{"get":{"operationId":"StorefrontController_buildPosCheckPayload","summary":"Get check print primitives","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The resolved print primitives","content":{"application/json":{"schema":{"type":"array","description":"Resolved print lines, in order.","items":{"type":"object","properties":{"kind":{"type":"string","description":"Primitive type — `text`, `twocol`, `divider`, `loop`, `qr`, `barcode`.","example":"twocol"},"text":{"type":"string","example":"Cola 330ml"},"bold":{"type":"boolean","example":false},"size":{"type":"string","description":"Relative size hint.","example":"normal"},"lines":{"type":"array","items":{"type":"string"},"description":"Column values for `twocol`."},"url":{"type":"string","description":"Payload for `qr` / `barcode`."}}}}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/check-payload","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"],"description":"The pre-payment check equivalent of `receipt-payload` — renders the check template for the tab and returns the primitive lines for a client that drives its own printer.\n\n#### Signature\n\n```http\nGET /storefront/pos/tab/{id}/check-payload (id: string) -> The resolved print primitives\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/print-check`"}},"/storefront/pos/tab/{id}/send-receipt":{"post":{"operationId":"StorefrontController_sendPosReceipt","summary":"Email or SMS a receipt","description":"Sends the receipt through the org's notification pipeline, using the same template chain as order-confirmation mail (org → shared-org → factory default).\n\nRecipients resolve in this order: an explicit `to`, `email` or `phone`, then the contact saved on the tab. The channel is inferred — email when an address is available, SMS otherwise — unless `mode` forces one.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/send-receipt (id: string, body) -> Dispatch result per channel\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | NO_RECIPIENT | No recipient — pass `to`, `email`, or `phone`, or save a contact on the tab first. | Neither the body nor the tab supplies a usable address, so no channel could be selected. | Send `to`, `email` or `phone`, or attach a customer to the tab when opening it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/print-receipt`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"description":"Recipient and channel. All fields optional when the tab has a saved contact.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Recipient email or phone, or several. Falls back to the tab contact.","example":"ada@example.com"},"email":{"type":"string","description":"Explicit email override.","example":"ada@example.com"},"phone":{"type":"string","description":"Explicit phone override.","example":"+15551234567"},"mode":{"type":"string","enum":["email","sms"],"description":"Force a channel instead of inferring it.","example":"email"}}},"examples":{"savedContact":{"summary":"Use the tab's saved contact","value":{}},"explicitEmail":{"summary":"Email a specific address","value":{"to":"ada@example.com","mode":"email"}},"sms":{"summary":"Force SMS","value":{"phone":"+15551234567","mode":"sms"}}}}}},"responses":{"201":{"description":"Dispatch result per channel","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"No recipient — pass `to`, `email`, or `phone`, or save a contact on the tab first. — Neither the body nor the tab supplies a usable address, so no channel could be selected.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No recipient — pass `to`, `email`, or `phone`, or save a contact on the tab first.","path":"/storefront/pos/tab/{id}/send-receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/send-receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/split":{"post":{"operationId":"StorefrontController_splitPosTab","summary":"Split a POS tab","description":"Moves lines off a tab onto one or more new tabs. Two mutually exclusive modes — pass exactly one:\n\n- **By item** — `itemIds` moves those specific lines to a single new tab. Returns `{ source, target }`.\n- **By count** — `parts` (≥ 2) distributes the lines across N tabs by descending value using a greedy bin-pack, with the source keeping the largest bin. Returns `{ source, targets[] }`.\n\nAlready-fired lines keep their `taskId`, so kitchen tickets are never disturbed by a split — only the parent order id moves. Partial payments stay on the source tab.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/split (id: string, body) -> The source tab plus the tab(s) created — `target` for a by-item split, `targets[]` for by-count\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Source order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | NOT_SPLITTABLE | Cannot split <status> order | The source tab is in a status that cannot be split. | Only an active tab can be split. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/settle`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Source tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"description":"Split instruction. Send `itemIds` or `parts`, not both.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"itemIds":{"type":"array","items":{"type":"string"},"description":"Line ids to move to a new tab (by-item split).","example":["li_7f3a"]},"parts":{"type":"number","minimum":2,"description":"Number of tabs to distribute across (by-count split).","example":3},"alias":{"type":"string","description":"Alias for the new tab(s).","example":"Table 7b"}}},"examples":{"byItem":{"summary":"Move two lines to a new tab","value":{"itemIds":["li_7f3a","li_8b2c"],"alias":"Table 7b"}},"byCount":{"summary":"Split three ways","description":"Source keeps the largest bin.","value":{"parts":3}}}}}},"responses":{"201":{"description":"The source tab plus the tab(s) created — `target` for a by-item split, `targets[]` for by-count","content":{"application/json":{"schema":{"type":"object","properties":{"source":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"target":{"type":"object","description":"By-item split only.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"targets":{"type":"array","items":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"description":"By-count split only."}}}}}},"400":{"description":"Cannot split <status> order — The source tab is in a status that cannot be split.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot split <status> order","path":"/storefront/pos/tab/{id}/split","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Source order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Source order not found","path":"/storefront/pos/tab/{id}/split","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/refund":{"post":{"operationId":"StorefrontController_refundPosTabPayment","summary":"Refund a payment on a POS tab","description":"Refunds one confirmed payment line, in full or in part, and records it on the tab.\n\n- **Cash** (method `cash`, gateway manual/offline): records the cash already handed back — send `cashReturned: true` once the guest has it.\n- **Card / gift card**: refunds through the payments refund first (Stripe back to the card, a gift card back onto the card that paid), then records it. If the provider refuses, nothing is recorded.\n\nThe payment line is kept and marked `refundedAmount`/`refunded`, a negative refund line is added, `amountPaid` goes down, and the tab becomes `refunded` once the whole sale is refunded. Net sales and tax are reversed in proportion (gift cards sold come back out of the gift card liability) and an `sf_refund` is written. If the journal cannot post, the refund is still recorded with `status: needs_attention` and the same call with the same `operationKey` replays it.\n\n**Idempotent on `operationKey`**: a retry with the same key returns the first refund and never refunds twice; the key must match the same payment and amount.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/refund (id: string, body) -> The tab and the refund record\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Anonymous in the code as it stands: StorefrontController is `@PublicRoute()` at class level and this handler does not override it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | OPERATION_KEY_REQUIRED | operationKey is required — a stable id for this refund (8–100 letters, digits, _ or -), reused if the call is retried | Missing or malformed `operationKey`. | — |\n| `409` | KEY_MISMATCH | This refund key belongs to a different payment or amount | The key was used for another payment or amount. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/settle`\n- `GET /storefront/order/{id}/payments`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["transactionId","amount","operationKey"],"properties":{"transactionId":{"type":"string","description":"The original payment line's `transactionId`."},"amount":{"type":"number","description":"Up to what is left refundable on that line, at most two decimals.","example":12.5},"operationKey":{"type":"string","description":"Stable id for this refund, 8–100 letters, digits, `_` or `-`. Reuse it on retry.","example":"rf_7Kq2M9xa"},"cashReturned":{"type":"boolean","description":"Cash refunds: must be true."},"reason":{"type":"string","example":"Wrong order"}}},"examples":{"card":{"summary":"Partial card refund","value":{"transactionId":"TXN-8KQ2","amount":12.5,"operationKey":"rf_7Kq2M9xa","reason":"Cold food"}},"cash":{"summary":"Cash refund","value":{"transactionId":"TXN-8KQ3","amount":20,"operationKey":"rf_7Kq2M9xb","cashReturned":true}}}}}},"responses":{"201":{"description":"The tab and the refund record","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}},"refund":{"type":"object","additionalProperties":true,"description":"id, operationKey, paymentId, amount, netSales, salesTax, method, gateway, ref, reason, status complete | needs_attention, issues[]."}}}}}},"400":{"description":"operationKey is required — a stable id for this refund (8–100 letters, digits, _ or -), reused if the call is retried — Missing or malformed `operationKey`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"operationKey is required — a stable id for this refund (8–100 letters, digits, _ or -), reused if the call is retried","path":"/storefront/pos/tab/{id}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This refund key belongs to a different payment or amount — The key was used for another payment or amount.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This refund key belongs to a different payment or amount","path":"/storefront/pos/tab/{id}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/assign-service-point":{"post":{"operationId":"StorefrontController_assignServicePoint","summary":"Assign a service point to a tab","description":"Seats a tab at a table or counter, setting `servicePointId` and optionally renaming the tab. The service point must belong to the same location as the tab — a mismatch is rejected rather than silently seating a tab across locations.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/assign-service-point (id: string, body) -> The updated tab\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | SERVICE_POINT_REQUIRED | servicePointId is required | `servicePointId` is missing. | Send the id of the service point to seat the tab at. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/release-service-point`\n- `POST /storefront/service-point/{id}/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"description":"The service point to seat the tab at.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["servicePointId"],"properties":{"servicePointId":{"type":"string","description":"Target `service_point`.","example":"sp_t7"},"alias":{"type":"string","description":"Optionally rename the tab as part of seating it.","example":"Table 7"}}},"example":{"servicePointId":"sp_t7","alias":"Table 7"}}}},"responses":{"201":{"description":"The updated tab","content":{"application/json":{"schema":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}},"400":{"description":"servicePointId is required — `servicePointId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"servicePointId is required","path":"/storefront/pos/tab/{id}/assign-service-point","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/assign-service-point","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/pos/tab/{id}/release-service-point":{"post":{"operationId":"StorefrontController_releaseServicePoint","summary":"Release a tab's service point","description":"Clears `servicePointId` from the tab and frees the table or counter for the next party. The tab itself stays open — releasing a service point is not the same as closing out.\n\n#### Signature\n\n```http\nPOST /storefront/pos/tab/{id}/release-service-point (id: string, body) -> The updated tab, with no service point held\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Releasing a tab that holds no service point is a no-op, not an error.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/assign-service-point`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"POS tab `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"description":"Optional release details.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","description":"Recorded with the release.","example":"Party moved to the patio"}}},"example":{}}}},"responses":{"201":{"description":"The updated tab, with no service point held","content":{"application/json":{"schema":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/pos/tab/{id}/release-service-point","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/service-point/{id}/status":{"post":{"operationId":"StorefrontController_setServicePointStatus","summary":"Set a service point status","description":"Updates the state of a table or counter directly — for example marking it dirty, out of service, or ready. This acts on the `service_point` record itself, independently of any tab seated there.\n\n#### Signature\n\n```http\nPOST /storefront/service-point/{id}/status (id: string, body) -> The updated service point\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | STATUS_REQUIRED | status is required | `status` is missing from the body. | Send the new status value. |\n| `404` | SERVICE_POINT_NOT_FOUND | Service point not found | No `service_point` in the org has that id. | Check the id. Note that the assign endpoint returns `400` for the same condition; this one returns `404`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/assign-service-point`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"`service_point` id.","schema":{"type":"string"},"example":"sp_t7"}],"requestBody":{"description":"The new status.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"New service point status, e.g. `available`, `occupied`, `dirty`, `out-of-service`.","example":"dirty"},"note":{"type":"string","description":"Optional note recorded with the change.","example":"Needs bussing"}}},"example":{"status":"dirty","note":"Needs bussing"}}}},"responses":{"201":{"description":"The updated service point","content":{"application/json":{"schema":{"type":"object","description":"A `service_point` record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"status is required — `status` is missing from the body.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"status is required","path":"/storefront/service-point/{id}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Service point not found — No `service_point` in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Service point not found","path":"/storefront/service-point/{id}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/order/{id}/items":{"post":{"operationId":"StorefrontController_addItemsToOrder","summary":"Add items to an order","description":"Appends lines to an open tab and recomputes `productCount`, `productSubtotal`, `subtotal`, `tax` and `amount`. Each item is resolved against the catalog by `sku`, so pricing and titles come from the product record rather than the client.\n\n#### Signature\n\n```http\nPOST /storefront/order/{id}/items (id: string, body) -> The updated order with recomputed totals\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Adding lines does not fire them. Send them to a workflow with `POST /storefront/order/{id}/fire`.\n- POS gate: adding lines checks the whole-register gate for the operator, then — for a product with `restriction.ageRestricted` / `restriction.permissionScope` (e.g. alcohol) — the gate with that `permissionScope`. A refusal is 423 `readiness_block` naming the product (`sku`, `productName`, `permissionScope`). Lines carry `permissionScope` and any `readinessOverride`; warnings return as `readiness.warnings`.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | ORDER_NOT_OPEN | Cannot add items to <status> order | The order is in a status that no longer accepts lines. | Open a new tab instead of amending a closed one. |\n| `423` | READINESS_BLOCK | <name> can't sell <product> (alcohol): <requirement titles>. | A requirement rule whose `enforcement.pos` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/{id}/fire`\n- `POST /storefront/pos/tab/{id}/settle`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Order `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"description":"The lines to add.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","description":"One entry per line. Sending the same `sku` twice creates two lines.","items":{"type":"object","required":["sku"],"properties":{"sku":{"type":"string","description":"Catalog SKU. Must resolve to an `sf_product`.","example":"DRK-COLA-330"},"quantity":{"type":"number","default":1,"example":2},"course":{"type":"string","description":"Service course for coursed dining.","example":"main"},"seat":{"type":"string","description":"Seat identifier.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"}}}},"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}},"examples":{"single":{"summary":"One line","value":{"items":[{"sku":"DRK-COLA-330","quantity":2}]}},"coursed":{"summary":"Coursed and seated","value":{"items":[{"sku":"FOO-STEAK","quantity":1,"course":"main","seat":"2","specialInstructions":"medium rare"}]}}}}}},"responses":{"201":{"description":"The updated order with recomputed totals","content":{"application/json":{"schema":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}},"400":{"description":"Cannot add items to <status> order — The order is in a status that no longer accepts lines.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot add items to <status> order","path":"/storefront/order/{id}/items","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/storefront/order/{id}/items","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/{id}/items","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't sell <product> (alcohol): <requirement titles>. — A requirement rule whose `enforcement.pos` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"pos","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/order/{id}/fire":{"post":{"operationId":"StorefrontController_fireOrderItems","summary":"Fire order items to a workflow","description":"Sends selected lines to a preparation workflow — kitchen, bar, lab, prep — and stamps the resulting `taskId` onto each fired line.\n\n**`taskId` is the definition of \"sent\".** Lines that already carry one are skipped, which makes the endpoint safe against double-clicks and stale client caches. If *every* requested line was already fired the request fails rather than silently doing nothing, so the operator gets told.\n\n#### Signature\n\n```http\nPOST /storefront/order/{id}/fire (id: string, body) -> The order with `taskId` stamped on the newly fired lines\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Partial success is normal: unfired lines in `itemIds` are fired and already-fired ones are skipped, with no error.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No `sf_order` in the org has the given `sk`. | Confirm the tab `sk`. `GET /storefront/pos/tabs` lists the open tabs for a location. |\n| `400` | ITEM_IDS_REQUIRED | itemIds required | `itemIds` is missing or empty. | Send at least one line id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/{id}/items`\n- `GET /storefront/order/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Order `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"description":"Which lines to fire, and where.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemIds"],"properties":{"itemIds":{"type":"array","items":{"type":"string"},"description":"Line ids from `productItems[].id`. Already-fired lines are ignored.","example":["li_7f3a","li_8b2c"]},"workflowId":{"type":"string","description":"Target workflow by id. Takes precedence over `workflowName`."},"workflowName":{"type":"string","default":"prep-pipeline","description":"Target workflow by name when `workflowId` is absent.","example":"kitchen-pipeline"},"course":{"type":"string","description":"Course stamped on the fired task.","example":"main"},"seat":{"type":"string","example":"2"},"specialInstructions":{"type":"string","description":"Carried onto the task ticket.","example":"allergy: shellfish"}}},"examples":{"kitchen":{"summary":"Fire two lines to the kitchen","value":{"itemIds":["li_7f3a","li_8b2c"],"workflowName":"kitchen-pipeline"}},"default":{"summary":"Fire to the default pipeline","description":"Omitting both workflow fields uses `prep-pipeline`.","value":{"itemIds":["li_7f3a"]}}}}}},"responses":{"201":{"description":"The order with `taskId` stamped on the newly fired lines","content":{"application/json":{"schema":{"type":"object","description":"A POS tab or web order (`sf_order`). POS tabs and web orders share one shape and one collection.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"status":{"type":"string","description":"Lifecycle status. `new` on open; terminal values close the tab.","example":"new"},"orderType":{"type":"string","example":"product"},"number":{"type":"string","description":"Human display number, stamped at tab open. Payments and refunds are recorded against this, never the `sk` — a tab missing it cannot be settled.","example":"A7K2M9QX4"},"alias":{"type":"string","description":"Operator-facing label (Table 7, Walk-up #3, Smith party).","example":"Table 7"},"businessLocationId":{"type":"string","description":"Location the tab belongs to.","example":"loc_downtown"},"servicePointId":{"type":"string","description":"Assigned table/counter, when one is held.","example":"sp_t7"},"productItems":{"type":"array","items":{"type":"object","description":"A line on the tab.","properties":{"id":{"type":"string","description":"Line id, unique within the order. This is what `itemIds` refers to.","example":"li_7f3a"},"sku":{"type":"string","example":"DRK-COLA-330"},"title":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price at the time the line was added.","example":12},"course":{"type":"string","description":"Service course, e.g. `starter`, `main`.","example":"main"},"seat":{"type":"string","description":"Seat identifier for coursed service.","example":"2"},"specialInstructions":{"type":"string","example":"no ice"},"taskId":{"type":"string","description":"Set once the line has been fired to a workflow. **The presence of `taskId` is what \"sent to the kitchen\" means** — it is the idempotency marker, and fired lines are skipped by subsequent fire requests.","example":"tsk_91b2"},"task":{"type":"object","additionalProperties":true,"description":"The workflow task, populated inline on task-enriched reads."}}}},"productCount":{"type":"number","example":2},"subtotal":{"type":"number","example":24},"productSubtotal":{"type":"number","example":24},"tax":{"type":"number","example":1.92},"discount":{"type":"number","example":0},"amount":{"type":"number","description":"Order total.","example":25.92},"amountPaid":{"type":"number","description":"Sum of recorded tenders, net of refunds.","example":0},"currency":{"type":"string","default":"USD","example":"USD"},"payments":{"type":"array","description":"Recorded tenders, newest last. Refunded lines keep `refunded: true` rather than being removed.","items":{"type":"object","properties":{"transactionId":{"type":"string","example":"txn_4a91"},"ref":{"type":"string","description":"Gateway or operator reference.","example":"ch_3Pabc"},"amount":{"type":"number","example":25.92},"method":{"type":"string","example":"card"},"gateway":{"type":"string","example":"stripe"},"tip":{"type":"number","example":3},"refunded":{"type":"boolean","example":false}}}},"customer":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}}}}}}}}},"400":{"description":"itemIds required — `itemIds` is missing or empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"itemIds required","path":"/storefront/order/{id}/fire","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No `sf_order` in the org has the given `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/{id}/fire","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · POS"]}},"/storefront/cart/mine":{"get":{"operationId":"StorefrontController_savedCart","summary":"Restore my saved cart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The saved cart, or null","content":{"application/json":{"schema":{"type":"object","description":"A shopping cart (`sf_cart`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"image":{"type":"string"},"options":{"type":"string","description":"Selected variant options.","example":"size:330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}}},"subtotal":{"type":"number","example":24},"discount":{"type":"number","example":0},"tax":{"type":"number","example":1.92},"total":{"type":"number","example":25.92},"currency":{"type":"string","example":"USD"},"couponCode":{"type":"string","description":"Applied coupon, when one validated.","example":"SUMMER20"}}}},"nullable":true}}}},"401":{"description":"Sign in to restore your saved cart — No customer session for this org (a staff/user token does not count).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to restore your saved cart","path":"/storefront/cart/mine","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Checkout"],"description":"The signed-in customer's saved cart (`sf_cart`), for restoring a basket on a new device. Identity comes only from the customer session — never from a supplied email or id — and the cart is scoped to the customer's shared account when they have one. Read-only: returns `null` rather than creating a cart.\n\n#### Signature\n\n```http\nGET /storefront/cart/mine () -> The saved cart, or null\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in to restore your saved cart | No customer session for this org (a staff/user token does not count). | — |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/cart/get/{authorid}/{cartid}`"}},"/storefront/cart/get/{authorid}/{cartid}":{"get":{"operationId":"StorefrontController_cart","summary":"Get a cart","description":"Returns a cart with its items and computed totals.\n\n**The `authorid` segment is ignored.** The handler resolves the cart from `cartid` and the authenticated caller only — the path parameter is a historical artefact. It is still required by the route, so pass any non-empty value, but do not expect it to scope the lookup.\n\n#### Signature\n\n```http\nGET /storefront/cart/get/{authorid}/{cartid} (authorid: string, cartid: string) -> The cart with items and totals\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Because `authorid` is not used, do not rely on it to isolate carts between customers.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/cart/update/{cartid}`\n- `POST /storefront/checkout-cart`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"cartid","required":true,"in":"path","description":"Cart id. Optional segment — omitted resolves the caller's current cart.","schema":{"type":"string"},"example":"cart_9f21"},{"name":"authorid","required":true,"in":"path","description":"Ignored by the handler. Required by the route; pass the customer id for readability.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"The cart with items and totals","content":{"application/json":{"schema":{"type":"object","description":"A shopping cart (`sf_cart`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"image":{"type":"string"},"options":{"type":"string","description":"Selected variant options.","example":"size:330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}}},"subtotal":{"type":"number","example":24},"discount":{"type":"number","example":0},"tax":{"type":"number","example":1.92},"total":{"type":"number","example":25.92},"currency":{"type":"string","example":"USD"},"couponCode":{"type":"string","description":"Applied coupon, when one validated.","example":"SUMMER20"}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Cart"]}},"/storefront/cart/update/{cartid}":{"post":{"operationId":"StorefrontController_cartUpdate","summary":"Update a cart","description":"Writes the cart contents and recomputes subtotal, discount, tax and total. This is the single mutation endpoint for carts — adding, changing quantity and removing a line are all expressed as an updated `items` array.\n\nSend a coupon with `couponCode` to have it validated and applied as part of the same call.\n\n#### Signature\n\n```http\nPOST /storefront/cart/update/{cartid} (cartid: string, body) -> The updated cart with recomputed totals\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- `items` replaces the stored array rather than merging. Read the cart, modify the list, send it back.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CART_NOT_FOUND | Cart not found | No cart in the org has that id. | Create a cart by updating one with a new id, or re-read the customer's cart. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/cart/get/{authorid}/{cartid}`\n- `GET /storefront/cart/clear/{cartid}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"cartid","required":true,"in":"path","description":"Cart id.","schema":{"type":"string"},"example":"cart_9f21"}],"requestBody":{"description":"The cart contents to store.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"image":{"type":"string"},"options":{"type":"string","description":"Selected variant options.","example":"size:330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}},"description":"The full line set. Replaces what is stored — send every line you want to keep."},"couponCode":{"type":"string","description":"Coupon to validate and apply.","example":"SUMMER20"},"currency":{"type":"string","example":"USD"}}},"examples":{"setItems":{"summary":"Set the cart contents","value":{"items":[{"sku":"DRK-COLA-330","quantity":2,"price":12}]}},"removeLine":{"summary":"Remove a line","description":"Omit it from `items` — there is no delete endpoint.","value":{"items":[]}},"withCoupon":{"summary":"Apply a coupon","value":{"items":[{"sku":"DRK-COLA-330","quantity":2,"price":12}],"couponCode":"SUMMER20"}}}}}},"responses":{"201":{"description":"The updated cart with recomputed totals","content":{"application/json":{"schema":{"type":"object","description":"A shopping cart (`sf_cart`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"image":{"type":"string"},"options":{"type":"string","description":"Selected variant options.","example":"size:330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}}},"subtotal":{"type":"number","example":24},"discount":{"type":"number","example":0},"tax":{"type":"number","example":1.92},"total":{"type":"number","example":25.92},"currency":{"type":"string","example":"USD"},"couponCode":{"type":"string","description":"Applied coupon, when one validated.","example":"SUMMER20"}}}}}}}},"404":{"description":"Cart not found — No cart in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Cart not found","path":"/storefront/cart/update/{cartid}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Cart"]}},"/storefront/cart/clear/{cartid}":{"get":{"operationId":"StorefrontController_cartClear","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"cartid","in":"path","required":true,"description":"Cart id.","schema":{"type":"string"},"example":"cart_9f21"}],"responses":{"200":{"description":"The emptied cart","content":{"application/json":{"schema":{"type":"object","description":"A shopping cart (`sf_cart`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"image":{"type":"string"},"options":{"type":"string","description":"Selected variant options.","example":"size:330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}}},"subtotal":{"type":"number","example":24},"discount":{"type":"number","example":0},"tax":{"type":"number","example":1.92},"total":{"type":"number","example":25.92},"currency":{"type":"string","example":"USD"},"couponCode":{"type":"string","description":"Applied coupon, when one validated.","example":"SUMMER20"}}}}}}}},"404":{"description":"Cart not found — No cart in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Cart not found","path":"/storefront/cart/clear/{cartid}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Cart"],"summary":"Clear a cart","description":"Empties the cart, leaving the record in place so the same id stays usable.\n\nThis mutation is mounted on `GET`, so anything that follows links — a crawler, a link preview, a browser prefetch — can empty a customer's cart. Do not expose the URL where it may be fetched automatically.\n\n#### Signature\n\n```http\nGET /storefront/cart/clear/{cartid} (cartid: string) -> The emptied cart\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CART_NOT_FOUND | Cart not found | No cart in the org has that id. | Create a cart by updating one with a new id, or re-read the customer's cart. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/cart/update/{cartid}`"}},"/storefront/order/email/{email}/{orderNumber}":{"get":{"operationId":"StorefrontController_orderByEmail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","in":"path","required":true,"description":"Email address used at checkout.","schema":{"type":"string"},"example":"ada@example.com"},{"name":"orderNumber","in":"path","required":true,"description":"The public order number (`data.number`), not the record `sk`.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"The matching order","content":{"application/json":{"schema":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/email/{email}/{orderNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"summary":"Look up an order by email","description":"Guest order lookup: resolves an order from the email address used at checkout plus the order number. This is the pairing behind \"track my order\" forms, where the customer has no account.\n\n#### Signature\n\n```http\nGET /storefront/order/email/{email}/{orderNumber} (email: string, orderNumber: string) -> The matching order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Knowing an email and an order number is sufficient to read the order. Rate-limit any public form built on this.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/order/get/{author}/{orderNumber}`"}},"/storefront/order/get/{author}/{orderNumber}":{"get":{"operationId":"StorefrontController_order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"author","in":"path","required":true,"description":"Customer email or username.","schema":{"type":"string"},"example":"ada@example.com"},{"name":"orderNumber","in":"path","required":true,"description":"The public order number (`data.number`), not the record `sk`.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"The matching order","content":{"application/json":{"schema":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/get/{author}/{orderNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"summary":"Get a customer's order","description":"Fetches one order, scoped to the customer who placed it. Both the author and the order number must match.\n\n#### Signature\n\n```http\nGET /storefront/order/get/{author}/{orderNumber} (author: string, orderNumber: string) -> The matching order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/order/email/{email}/{orderNumber}`"}},"/storefront/orders/get/{author}":{"get":{"operationId":"StorefrontController_orders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"author","in":"path","required":true,"description":"Customer email or username.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"The customer's orders","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"summary":"List a customer's orders","description":"Returns every order placed by the given customer, identified by the email or username recorded as the order author.\n\n#### Signature\n\n```http\nGET /storefront/orders/get/{author} (author: string) -> The customer's orders\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- This is a customer-scoped read on a public route — treat the author value as sensitive and do not expose it in client-side URLs you did not construct.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/order/get/{author}/{orderNumber}`"}},"/storefront/order/remove/{author}/{orderNumber}":{"get":{"operationId":"StorefrontController_orderRemove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"author","in":"path","required":true,"description":"Customer email or username.","schema":{"type":"string"},"example":"ada@example.com"},{"name":"orderNumber","in":"path","required":true,"description":"The public order number. Optional segment.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/remove/{author}/{orderNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"summary":"Remove an order","description":"Deletes an order belonging to the given customer.\n\nNote that this destructive operation is mounted on `GET`, so it can be triggered by anything that follows a link — a crawler, a prefetch, a preview. Never place this URL where it can be fetched automatically.\n\n#### Signature\n\n```http\nGET /storefront/order/remove/{author}/{orderNumber} (author: string, orderNumber: string) -> Removal result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Prefer `POST /storefront/order/cancel/{orderNumber}`, which preserves the record and its audit trail.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/cancel/{orderNumber}`"}},"/storefront/order/send-welcome/{orderNumber}":{"get":{"operationId":"StorefrontController_orderSendWelcome","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","in":"path","required":true,"description":"The public order number (`data.number`), not the record `sk`.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/send-welcome/{orderNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"summary":"Send the order confirmation email","description":"Re-sends the order confirmation to the address on the order, through the org's notification pipeline (template chain: org → shared-org → factory default). Useful when a customer did not receive the original.\n\n#### Signature\n\n```http\nGET /storefront/order/send-welcome/{orderNumber} (orderNumber: string) -> Dispatch result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Sends every time it is called — there is no once-only guard, so a retry mails the customer again.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n\nPlus the standard platform errors: `429`, `500`."}},"/storefront/order/refund/{orderNumber}":{"post":{"operationId":"StorefrontController_orderRefund","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","in":"path","required":true,"description":"The public order number (`data.number`), not the record `sk`.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"201":{"description":"The refund result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}}}}}}},"400":{"description":"Order has no payment to refund — The order has never been paid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Order has no payment to refund","path":"/storefront/order/refund/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/refund/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"summary":"Refund an order","description":"Refunds a paid order, in full or in part, without cancelling it. Use this for goodwill refunds and returns where the order itself stands.\n\nFor refunding one specific tender on a POS tab, use `POST /storefront/pos/tab/{id}/refund`, which targets an individual payment line.\n\n#### Signature\n\n```http\nPOST /storefront/order/refund/{orderNumber} (orderNumber: string, body) -> The refund result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | NO_PAYMENT | Order has no payment to refund | The order has never been paid. | Cancel the order instead — there is nothing to return. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pos/tab/{id}/refund`\n- `POST /storefront/order/cancel/{orderNumber}`","requestBody":{"description":"Refund details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Amount to refund. Defaults to the full paid amount. Must be positive and within the unrefunded balance.","example":25},"reason":{"type":"string","description":"Recorded with the refund.","example":"Damaged in transit"}}},"examples":{"full":{"summary":"Full refund","value":{"reason":"Damaged in transit"}},"partial":{"summary":"Partial refund","value":{"amount":25,"reason":"Goodwill — late delivery"}}}}}}}},"/storefront/order/cancel/{orderNumber}":{"post":{"operationId":"StorefrontController_orderCancel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","in":"path","required":true,"description":"The public order number (`data.number`), not the record `sk`.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"201":{"description":"The cancelled order, with refund details when one was issued","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"refundResult":{"type":"object","additionalProperties":true,"nullable":true,"description":"The refund that was issued, or `null` when the order was unpaid."},"message":{"type":"string","example":"Order cancelled"}}}}}},"400":{"description":"Cannot cancel order with status \"<status>\". Orders that are shipped, in transit, or delivered cannot be cancelled. — The order status is one of `shipped`, `in_transit`, `delivered`, `cancelled` or `refunded`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot cancel order with status \"<status>\". Orders that are shipped, in transit, or delivered cannot be cancelled.","path":"/storefront/order/cancel/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/cancel/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"summary":"Cancel an order","description":"Cancels an order and, when it has been paid, triggers a refund as part of the same operation.\n\nCancellation is blocked once goods are in the customer's hands or on their way — a shipped, in-transit or delivered order must be returned rather than cancelled.\n\n#### Signature\n\n```http\nPOST /storefront/order/cancel/{orderNumber} (orderNumber: string, body) -> The cancelled order, with refund details when one was issued\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Cancelling a paid order refunds it automatically — do not also call the refund endpoint, or you will attempt to refund twice.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | NOT_CANCELLABLE | Cannot cancel order with status \"<status>\". Orders that are shipped, in transit, or delivered cannot be cancelled. | The order status is one of `shipped`, `in_transit`, `delivered`, `cancelled` or `refunded`. | Process a return or a refund instead. `POST /storefront/order/refund/{orderNumber}` refunds without cancelling. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/refund/{orderNumber}`","requestBody":{"description":"Optional cancellation details.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Why the order was cancelled. Recorded in the status history.","example":"Customer changed their mind"},"note":{"type":"string","description":"Additional note."}}},"example":{"reason":"Customer changed their mind"}}}}}},"/storefront/order/process/{orderNumber}":{"post":{"operationId":"StorefrontController_orderProcess","summary":"Mark an order as being fulfilled","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The updated order and a confirmation message","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"400":{"description":"Cannot process order with status \"<status>\" — The order's current status is one of `cancelled`, `refunded`, `shipped`, `delivered`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot process order with status \"<status>\"","path":"/storefront/order/process/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/process/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Moves the order to `processing` and stamps `processedAt`. This is the first step of fulfilment — the point where a warehouse picks up the order.\n\nUnlike `set-status`, this transition fires the automation and events attached to processing.\n\n#### Signature\n\n```http\nPOST /storefront/order/process/{orderNumber} (orderNumber: string, body) -> The updated order and a confirmation message\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | INVALID_TRANSITION | Cannot process order with status \"<status>\" | The order's current status is one of `cancelled`, `refunded`, `shipped`, `delivered`. | An order that has already shipped or been closed cannot re-enter fulfilment. Use `set-status` if you must override. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/ship/{orderNumber}`\n- `POST /storefront/order/set-status/{orderNumber}`","requestBody":{"description":"Optional note recorded in the status history.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","description":"Recorded against this transition in `statusHistory`.","example":"Picked and packed"}}},"examples":{"withNote":{"summary":"With a note","value":{"note":"Picked and packed"}},"bare":{"summary":"No note","value":{}}}}}}}},"/storefront/order/ship/{orderNumber}":{"post":{"operationId":"StorefrontController_orderShip","summary":"Ship an order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The updated order and a confirmation message","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"400":{"description":"Cannot ship order with status \"<status>\" — The order's current status is one of `cancelled`, `refunded`, `delivered`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot ship order with status \"<status>\"","path":"/storefront/order/ship/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/ship/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Records a shipment and moves the order to `shipped`, stamping `shippedAt`.\n\nEach call appends an entry to `shippingInfo[]` rather than replacing it, so an order fulfilled in several parcels accumulates one entry per shipment. Pass `items` to record which lines went in which parcel.\n\n#### Signature\n\n```http\nPOST /storefront/order/ship/{orderNumber} (orderNumber: string, body) -> The updated order and a confirmation message\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- An already-`shipped` order can be shipped again — that is how multi-parcel fulfilment is recorded.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | INVALID_TRANSITION | Cannot ship order with status \"<status>\" | The order's current status is one of `cancelled`, `refunded`, `delivered`. | A cancelled, refunded or already-delivered order cannot be shipped. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/deliver/{orderNumber}`","requestBody":{"description":"Shipment details. `carrier` and `tracking` are both required.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["carrier","tracking"],"properties":{"carrier":{"type":"string","description":"Carrier handling the shipment.","example":"ups"},"tracking":{"type":"string","description":"Tracking number.","example":"1Z999AA10123456784"},"service":{"type":"string","description":"Service level.","example":"ground"},"cost":{"type":"number","description":"Shipping cost, for reporting.","example":12.5},"items":{"type":"array","description":"Lines included in this parcel. Omit when the whole order ships together.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","example":2}}}},"note":{"type":"string","description":"Recorded in the status history.","example":"Left with front desk"}}},"examples":{"whole":{"summary":"Ship the whole order","value":{"carrier":"ups","tracking":"1Z999AA10123456784","service":"ground","cost":12.5}},"partial":{"summary":"Ship part of the order","description":"Call again with the remaining lines to add a second shipment.","value":{"carrier":"ups","tracking":"1Z999AA10123456784","items":[{"sku":"DRK-COLA-330","quantity":2}]}}}}}}}},"/storefront/order/deliver/{orderNumber}":{"post":{"operationId":"StorefrontController_orderDeliver","summary":"Mark an order as delivered","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The updated order and a confirmation message","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"400":{"description":"Cannot mark as delivered - order status is \"<status>\" — The order is not in `shipped`, `in_transit` or `out_for_delivery`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot mark as delivered - order status is \"<status>\"","path":"/storefront/order/deliver/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/deliver/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Moves the order to `delivered` and stamps `deliveredAt`.\n\nThis transition is gated the opposite way to the others: rather than blocking a set of statuses it **requires** the order to already be in transit — `shipped`, `in_transit` or `out_for_delivery`. An order that was never marked shipped cannot be marked delivered.\n\n#### Signature\n\n```http\nPOST /storefront/order/deliver/{orderNumber} (orderNumber: string, body) -> The updated order and a confirmation message\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | NOT_IN_TRANSIT | Cannot mark as delivered - order status is \"<status>\" | The order is not in `shipped`, `in_transit` or `out_for_delivery`. | Ship the order first, or use `set-status` for a flow that skips shipping — local pickup, for instance. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/ship/{orderNumber}`\n- `POST /storefront/order/complete/{orderNumber}`","requestBody":{"description":"Optional note recorded in the status history.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","description":"Recorded against this transition in `statusHistory`.","example":"Signed for by Ada"}}},"examples":{"withNote":{"summary":"With a note","value":{"note":"Signed for by Ada"}},"bare":{"summary":"No note","value":{}}}}}}}},"/storefront/order/hold/{orderNumber}":{"post":{"operationId":"StorefrontController_orderHold","summary":"Put an order on hold","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The held order and a confirmation message","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"400":{"description":"Cannot hold order with status \"<status>\" — The order's current status is one of `cancelled`, `refunded`, `delivered`, `completed`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot hold order with status \"<status>\"","path":"/storefront/order/hold/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/hold/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Pauses an order: sets the status to `on_hold`, records `holdReason` and `heldAt`, and saves the current status in `previousStatus` so the order can be restored exactly where it left off.\n\nA `reason` is required — a held order with no explanation is not useful to whoever picks it up next.\n\n#### Signature\n\n```http\nPOST /storefront/order/hold/{orderNumber} (orderNumber: string, body) -> The held order and a confirmation message\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Holding an already-held order overwrites `previousStatus` with `on_hold`, so release would restore it to `on_hold`. Check the status before holding.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | INVALID_TRANSITION | Cannot hold order with status \"<status>\" | The order's current status is one of `cancelled`, `refunded`, `delivered`, `completed`. | A closed order cannot be held. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/release/{orderNumber}`","requestBody":{"description":"Why the order is being held.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Stored as `holdReason` and recorded in the status history.","example":"Awaiting stock"}}},"example":{"reason":"Awaiting stock"}}}}}},"/storefront/order/release/{orderNumber}":{"post":{"operationId":"StorefrontController_orderRelease","summary":"Release an order from hold","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The released order and a confirmation message","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"400":{"description":"Order is not on hold — The order status is anything other than `on_hold`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Order is not on hold","path":"/storefront/order/release/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/release/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Restores a held order to the status it had before the hold, clearing `previousStatus` and `holdReason`. When no previous status was recorded the order falls back to `processing`.\n\n#### Signature\n\n```http\nPOST /storefront/order/release/{orderNumber} (orderNumber: string) -> The released order and a confirmation message\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | NOT_ON_HOLD | Order is not on hold | The order status is anything other than `on_hold`. | Only a held order can be released. Read the order first if you are unsure of its state. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/hold/{orderNumber}`"}},"/storefront/order/complete/{orderNumber}":{"post":{"operationId":"StorefrontController_orderComplete","summary":"Complete an order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The completed order and a confirmation message","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"400":{"description":"Cannot complete order - must be delivered first — The order status is anything other than `delivered`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete order - must be delivered first","path":"/storefront/order/complete/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/complete/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Closes the order, moving it to `completed` and stamping `completedAt`. This is the terminal step of a successful order.\n\nThe order **must** be `delivered` first — completion is not a shortcut past the rest of the lifecycle.\n\n#### Signature\n\n```http\nPOST /storefront/order/complete/{orderNumber} (orderNumber: string) -> The completed order and a confirmation message\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | NOT_DELIVERED | Cannot complete order - must be delivered first | The order status is anything other than `delivered`. | Mark the order delivered first, or use `set-status` to close an order that never followed the ship → deliver path. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/deliver/{orderNumber}`"}},"/storefront/order/set-status/{orderNumber}":{"post":{"operationId":"StorefrontController_orderSetStatus","summary":"Set an order status directly","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The updated order and a confirmation message","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"400":{"description":"Invalid status \"<status>\" — `status` is missing, or is not one of the recognised values.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid status \"<status>\"","path":"/storefront/order/set-status/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/set-status/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Operator override. Sets `data.status`, stamps the matching timestamp field and appends to the status history — and does nothing else.\n\n**It deliberately skips both the preconditions and the automation** of the dedicated transitions: no delivery emails, no workflow advance, no step validation. Reach for it to correct a mistaken state, or to drive an order that never follows ship → deliver (local pickup, digital goods). Use the dedicated endpoints whenever you want the side effects.\n\nThe status must be one of the recognised values; anything else is rejected rather than stored.\n\n#### Signature\n\n```http\nPOST /storefront/order/set-status/{orderNumber} (orderNumber: string, body) -> The updated order and a confirmation message\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- No events fire. If a customer should be notified of this change, send the notification yourself.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n| `400` | INVALID_STATUS | Invalid status \"<status>\" | `status` is missing, or is not one of the recognised values. | Use one of: `new`, `awaiting-payment`, `paid-partial`, `paid`, `confirmed`, `processing`, `shipped`, `in_transit`, `out_for_delivery`, `delivered`, `completed`, `on_hold`, `failed`, `returned`, `refunded`, `cancelled`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/order/process/{orderNumber}`\n- `POST /storefront/order/deliver/{orderNumber}`","requestBody":{"description":"The status to set.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"description":"The new status. Must be one of the recognised values.","example":"completed"},"note":{"type":"string","description":"Recorded in the status history.","example":"Collected in store"}}},"examples":{"localPickup":{"summary":"Close a local-pickup order","description":"Skips ship → deliver, which local pickup never goes through.","value":{"status":"completed","note":"Collected in store"}},"correction":{"summary":"Correct a mistaken status","value":{"status":"processing","note":"Marked shipped in error"}}}}}}}},"/storefront/order/update/{orderNumber}":{"post":{"operationId":"StorefrontController_orderUpdateInfo","summary":"Update order details","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","schema":{"type":"string"},"description":"The public order number (`data.number`), not the record `sk`.","example":"A7K2M9QX4"}],"responses":{"201":{"description":"The updated order","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"An order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Public order number — the identifier these endpoints take.","example":"A7K2M9QX4"},"status":{"type":"string","enum":["new","awaiting-payment","paid-partial","paid","confirmed","processing","shipped","in_transit","out_for_delivery","delivered","completed","on_hold","failed","returned","refunded","cancelled"],"example":"processing"},"amount":{"type":"number","example":129.99},"amountPaid":{"type":"number","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"statusHistory":{"type":"array","description":"Append-only audit trail. Each transition records where it came from, where it went, when, and any note.","items":{"type":"object","properties":{"from":{"type":"string","example":"paid"},"to":{"type":"string","example":"processing"},"note":{"type":"string","example":"Picked and packed"},"at":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}},"shippingInfo":{"type":"array","description":"One entry per shipment. An order shipped in parts accumulates several.","items":{"type":"object","properties":{"id":{"type":"string","example":"ship-1756476202118"},"carrier":{"type":"string","example":"ups"},"tracking":{"type":"string","example":"1Z999AA10123456784"},"service":{"type":"string","example":"ground"},"cost":{"type":"number","example":12.5},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}}},"previousStatus":{"type":"string","description":"Set while an order is `on_hold` so release can restore it. Removed on release.","example":"processing"},"holdReason":{"type":"string","description":"Present only while on hold.","example":"Awaiting stock"},"paidAt":{"type":"string","format":"date-time"},"fulfilledAt":{"type":"string","format":"date-time"},"shippedAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}}},"message":{"type":"string","description":"Human-readable confirmation of what happened.","example":"Order placed on hold"}}}}}},"404":{"description":"Order not found — No order in the org has that public order number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order not found","path":"/storefront/order/update/{orderNumber}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Orders"],"description":"Edits the non-lifecycle fields of an order — addresses, notes and tags. It does not change the status; use the lifecycle endpoints for that.\n\nOnly the fields present in the body are written, so a partial update leaves everything else intact.\n\n#### Signature\n\n```http\nPOST /storefront/order/update/{orderNumber} (orderNumber: string, body) -> The updated order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- `tags` replaces the whole array rather than merging — send the full set you want to keep.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORDER_NOT_FOUND | Order not found | No order in the org has that public order number. | Confirm the number. These endpoints take `data.number`, not the record `sk`. |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"description":"Fields to update. All optional; omitted fields are left unchanged.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shippingInfo":{"type":"object","additionalProperties":true,"description":"Shipping details."},"shippingAddress":{"type":"object","additionalProperties":true,"description":"Delivery address."},"billingAddress":{"type":"object","additionalProperties":true,"description":"Billing address."},"notes":{"type":"string","description":"Customer-visible notes.","example":"Leave with the neighbour"},"internalNotes":{"type":"string","description":"Operator-only notes. Not shown to the customer.","example":"VIP — expedite"},"tags":{"type":"array","items":{"type":"string"},"description":"Replaces the existing tags.","example":["vip","fragile"]}}},"examples":{"address":{"summary":"Correct the delivery address","value":{"shippingAddress":{"line1":"12 Ada Way","city":"London","postcode":"E1 6AN","country":"GB"}}},"notes":{"summary":"Add an internal note and tags","value":{"internalNotes":"VIP — expedite","tags":["vip","fragile"]}}}}}}}},"/storefront/subscriptions/get/{author}/{subscriptionid}":{"get":{"operationId":"StorefrontController_getSubscriptions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"author","in":"path","required":true,"description":"Customer email or username.","schema":{"type":"string"},"example":"ada@example.com"},{"name":"subscriptionid","in":"path","required":true,"description":"Subscription number. Omit to list every subscription for the customer.","schema":{"type":"string"},"example":"SUB-4821"},{"name":"enrich","in":"query","required":false,"description":"Resolve linked plan and product records inline.","schema":{"type":"boolean","default":false},"example":true}],"responses":{"200":{"description":"The customer's subscriptions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A subscription.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Subscriptions"],"summary":"Get a customer's subscriptions","description":"Returns the subscriptions belonging to a customer. Supply `subscriptionid` to fetch one; omit the segment to list them all.\n\nPass `enrich=true` to resolve the linked plan and product records inline instead of returning bare references.\n\n#### Signature\n\n```http\nGET /storefront/subscriptions/get/{author}/{subscriptionid} (author: string, subscriptionid: string, enrich?: boolean) -> The customer's subscriptions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/update-subscription`"}},"/storefront/verify-payment/{provider}/{configId}/{paymentId}":{"get":{"operationId":"StorefrontController_verifyPayment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"provider","in":"path","required":true,"description":"Payment provider.","schema":{"type":"string"},"example":"stripe"},{"name":"configId","in":"path","required":true,"description":"Which configured gateway instance to verify against.","schema":{"type":"string"},"example":"gw_live_1"},{"name":"paymentId","in":"path","required":true,"description":"The provider's payment identifier.","schema":{"type":"string"},"example":"pi_3PabcXYZ"}],"responses":{"200":{"description":"The verification result from the provider","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"verified":{"type":"boolean","example":true},"status":{"type":"string","example":"succeeded"},"amount":{"type":"number","example":129.99}}}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/verify-payment/{provider}/{configId}/{paymentId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"summary":"Verify a payment with the provider","description":"Asks the provider directly whether a payment succeeded, rather than trusting the client. Use it to confirm a redirect-based payment when the customer returns to your site.\n\n#### Signature\n\n```http\nGET /storefront/verify-payment/{provider}/{configId}/{paymentId} (provider: string, configId: string, paymentId: string) -> The verification result from the provider\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Anonymous in the code as it stands: StorefrontController is `@PublicRoute()` at class level, and the handler's `@Roles(User)` is never checked because the guard returns early for public routes.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`."}},"/storefront/stripe/intent":{"post":{"operationId":"StorefrontController_stripePaymentIntent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The PaymentIntent, including `client_secret`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string","example":"pi_3PabcXYZ"},"client_secret":{"type":"string","description":"Pass to the browser SDK. Never log it."},"status":{"type":"string","example":"requires_payment_method"}}}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/stripe/intent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"summary":"Create a Stripe PaymentIntent","description":"Creates an online (card-not-present) PaymentIntent and returns its client secret for the browser Stripe SDK to confirm.\n\n#### Signature\n\n```http\nPOST /storefront/stripe/intent (body) -> The PaymentIntent, including `client_secret`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/stripe/capture`","requestBody":{"description":"Intent details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Amount in the currency's major unit.","example":129.99},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"orderId":{"type":"string","description":"Order to associate the intent with."}}},"example":{"amount":129.99,"currency":"USD","email":"ada@example.com"}}}}}},"/storefront/stripe/terminal/connection-token":{"post":{"operationId":"StorefrontController_stripeTerminalConnectionToken","summary":"Get a Stripe Terminal connection token","description":"Returns a single-use token that the Terminal SDK uses to initialise a card reader (BLE M2, WisePOS, …).\n\nThe token rotates on every call — fetch a fresh one each time the SDK asks, and never cache it.\n\n#### Signature\n\n```http\nPOST /storefront/stripe/terminal/connection-token () -> A single-use connection token\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/stripe/terminal/intent`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"A single-use connection token","content":{"application/json":{"schema":{"type":"object","properties":{"secret":{"type":"string","description":"Hand straight to the Terminal SDK."}}}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/stripe/terminal/connection-token","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"]}},"/storefront/stripe/terminal/location/ensure":{"post":{"operationId":"StorefrontController_stripeTerminalEnsureLocation","summary":"Find or create a Stripe Terminal location","description":"Maps one of your business locations onto a Stripe Terminal Location, creating it if it does not exist yet. The client sends only `businessLocationId`; the server reads the authoritative title and address from the location record, so Stripe never receives address data typed on a device.\n\nIf the location record is missing address fields Stripe requires, the request is refused with a message naming what to fix rather than creating a half-configured Location.\n\n#### Signature\n\n```http\nPOST /storefront/stripe/terminal/location/ensure (body) -> The existing or newly created Stripe Terminal Location\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Safe to call repeatedly — an existing matching Location is returned rather than duplicated.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | LOCATION_INCOMPLETE | Location is missing required address fields | The business location record lacks an address field Stripe requires. | Complete the address on the location record — the error names the missing fields — then retry. |\n| `404` | LOCATION_NOT_FOUND | Business location not found | `businessLocationId` does not resolve in the org. | Check the location id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/stripe/terminal/connection-token`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Which business location to map.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["businessLocationId"],"properties":{"businessLocationId":{"type":"string","description":"Your location record id.","example":"loc_downtown"}}},"example":{"businessLocationId":"loc_downtown"}}}},"responses":{"201":{"description":"The existing or newly created Stripe Terminal Location","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string","example":"tml_Fabc123"},"display_name":{"type":"string","example":"Downtown"}}}}}},"400":{"description":"Location is missing required address fields — The business location record lacks an address field Stripe requires.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Location is missing required address fields","path":"/storefront/stripe/terminal/location/ensure","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Business location not found — `businessLocationId` does not resolve in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Business location not found","path":"/storefront/stripe/terminal/location/ensure","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"]}},"/storefront/stripe/terminal/intent":{"post":{"operationId":"StorefrontController_stripeTerminalIntent","summary":"Create a card-present PaymentIntent","description":"Creates a PaymentIntent restricted to `card_present`, for a physical reader driven by the Terminal SDK.\n\nSet `captureMethod: \"manual\"` to authorise now and capture later — the tip-adjustment flow — then complete it with `POST /storefront/stripe/capture`.\n\n#### Signature\n\n```http\nPOST /storefront/stripe/terminal/intent (body) -> The card-present PaymentIntent\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/stripe/capture`\n- `POST /storefront/pos/tab/{id}/settle`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Card-present intent details.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"number","example":25.92},"currency":{"type":"string","example":"USD"},"orderId":{"type":"string","description":"POS tab or order this payment belongs to.","example":"66f1a2b3c4d5e6f708192a3b"},"email":{"type":"string","example":"ada@example.com"},"captureMethod":{"type":"string","enum":["automatic","manual"],"default":"automatic","description":"`manual` authorises now and captures later, which is what tip adjustment needs.","example":"manual"}}},"examples":{"immediate":{"summary":"Capture immediately","value":{"amount":25.92,"currency":"USD","orderId":"66f1a2b3c4d5e6f708192a3b"}},"authorizeOnly":{"summary":"Authorise for later capture","description":"Use with `stripe/capture` once the tip is known.","value":{"amount":25.92,"currency":"USD","captureMethod":"manual"}}}}}},"responses":{"201":{"description":"The card-present PaymentIntent","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/stripe/terminal/intent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"]}},"/storefront/stripe/capture":{"post":{"operationId":"StorefrontController_stripeCapture","summary":"Capture an authorized PaymentIntent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Which intent to capture, and how much.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentIntentId"],"properties":{"paymentIntentId":{"type":"string","description":"The authorised PaymentIntent.","example":"pi_3PabcXYZ"},"amount":{"type":"number","description":"Amount to capture. Defaults to the full authorised amount.","example":28.92}}},"examples":{"full":{"summary":"Capture the full authorisation","value":{"paymentIntentId":"pi_3PabcXYZ"}},"withTip":{"summary":"Capture including a tip","value":{"paymentIntentId":"pi_3PabcXYZ","amount":28.92}}}}}},"responses":{"201":{"description":"The captured PaymentIntent","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"PaymentIntent is not in a capturable state — The intent was created with automatic capture, was already captured, or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"PaymentIntent is not in a capturable state","path":"/storefront/stripe/capture","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"description":"Captures a PaymentIntent that was created with `captureMethod: \"manual\"`. Pass `amount` to capture less than was authorised, or more where the gateway permits it — this is how a tip added after the card is presented gets charged.\n\n#### Signature\n\n```http\nPOST /storefront/stripe/capture (body) -> The captured PaymentIntent\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_CAPTURABLE | PaymentIntent is not in a capturable state | The intent was created with automatic capture, was already captured, or has expired. | Only intents created with `captureMethod: \"manual\"` and still authorised can be captured. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/stripe/terminal/intent`"}},"/storefront/stripe/subscription-session":{"post":{"operationId":"StorefrontController_stripeSubscriptionSession","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-host","in":"header","required":false,"description":"Client host, used to build the return URLs.","schema":{"type":"string"},"example":"shop.example.com"},{"name":"x-client-protocol","in":"header","required":false,"description":"Protocol for the return URLs.","schema":{"type":"string","default":"http"},"example":"https"}],"responses":{"201":{"description":"The subscription session, including the redirect URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"url":{"type":"string"}}}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/stripe/subscription-session","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Subscriptions"],"summary":"Create a Stripe subscription session","description":"Creates a hosted Stripe Checkout session in subscription mode and returns the redirect URL. Use this to start a recurring plan; use `stripe/checkout-session` for one-off purchases.\n\n#### Signature\n\n```http\nPOST /storefront/stripe/subscription-session (body) -> The subscription session, including the redirect URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/subscriptions/get/{author}/{subscriptionid}`","requestBody":{"description":"Subscription session details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","description":"Subscription plan name.","example":"pro-monthly"},"email":{"type":"string","example":"ada@example.com"},"successUrl":{"type":"string","example":"https://shop.example.com/welcome"},"cancelUrl":{"type":"string","example":"https://shop.example.com/pricing"}}},"example":{"plan":"pro-monthly","email":"ada@example.com"}}}}}},"/storefront/stripe/checkout-session":{"post":{"operationId":"StorefrontController_stripeCheckSession","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The session, including the redirect URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string","example":"cs_test_abc"},"url":{"type":"string","description":"Redirect the customer here."}}}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/stripe/checkout-session","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"summary":"Create a Stripe Checkout session","description":"Creates a hosted Stripe Checkout session for a one-off purchase and returns the URL to redirect the customer to.\n\n#### Signature\n\n```http\nPOST /storefront/stripe/checkout-session (body) -> The session, including the redirect URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Confirm the outcome with `GET /storefront/verify-payment/...` when the customer returns — do not trust the redirect alone.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/stripe/subscription-session`","requestBody":{"description":"Session details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"image":{"type":"string"},"options":{"type":"string","description":"Selected variant options.","example":"size:330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}},"description":"Lines to bill."},"successUrl":{"type":"string","description":"Where Stripe returns the customer on success.","example":"https://shop.example.com/thanks"},"cancelUrl":{"type":"string","description":"Where Stripe returns the customer on cancellation.","example":"https://shop.example.com/cart"},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"}}},"example":{"items":[{"sku":"DRK-COLA-330","quantity":2,"price":12}],"successUrl":"https://shop.example.com/thanks","cancelUrl":"https://shop.example.com/cart","currency":"USD"}}}}}},"/storefront/paypal/config":{"get":{"operationId":"StorefrontController_paypalConfig","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Public PayPal fields, or an empty body","content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"configured":{"type":"boolean"},"data":{"type":"object","properties":{"provider":{"type":"string","example":"PayPalProvider"},"clientId":{"type":"string"},"sandbox":{"type":"boolean"}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"summary":"Get the PayPal configuration","description":"What the browser PayPal SDK needs for this org: the public fields of its active PayPal integration only — `data.provider`, `data.clientId`, `data.sandbox` — plus `configured` (false when the app secret is missing, so the button should not be offered). Credentials never leave the server. With no active PayPal config the response is an empty 200.\n\n#### Signature\n\n```http\nGET /storefront/paypal/config () -> Public PayPal fields, or an empty body\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Prefer `GET /storefront/payment-gateways`, which returns only the public fields of each gateway including PayPal's `clientId` and `sandbox`.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/payment-gateways`"}},"/storefront/payments/charge-link":{"post":{"operationId":"StorefrontController_createChargeLink","summary":"Create a standalone charge and its payment link","description":"A charge with no invoice behind it: writes a pending `sf_transaction` (type `charge`, source `take-payment`) for `amount` and returns the URL the customer pays at — the site's Payment page with `?ref=<id>`, which is also what the QR encodes. `url` is null, with a `reason`, when the site has no Payment page configured (Site Config › Site Features).\n\n#### Signature\n\n```http\nPOST /storefront/payments/charge-link (body) -> The charge and its link\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Anonymous: StorefrontController is `@PublicRoute()` at class level and this handler does not override it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | AMOUNT_REQUIRED | An amount greater than zero is required | `amount` is missing, not a number, or not positive. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/payments/charge-link/{id}/send`\n- `GET /storefront/payments/charge-link/{id}/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The charge and its link","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string","description":"Same as id."},"amount":{"type":"number"},"currency":{"type":"string"},"url":{"type":"string","nullable":true,"example":"https://shop.example.com/pay?ref=66f1a2b3c4d5"},"reason":{"type":"string"}}}}}},"400":{"description":"An amount greater than zero is required — `amount` is missing, not a number, or not positive.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An amount greater than zero is required","path":"/storefront/payments/charge-link","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","example":45},"currency":{"type":"string","default":"USD"},"description":{"type":"string","example":"Deposit for event booking"},"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}},"example":{"amount":45,"currency":"USD","description":"Deposit for event booking","name":"Ada Lovelace","email":"ada@example.com"}}}}}},"/storefront/payments/charge-link/{id}/send":{"post":{"operationId":"StorefrontController_sendChargeLink","summary":"Send a charge's payment link","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Charge id (`sf_transaction` sk) from POST /storefront/payments/charge-link."}],"responses":{"201":{"description":"Channels sent and the link","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"array","items":{"type":"string"}},"url":{"type":"string","nullable":true}}}}}},"400":{"description":"No Payment page is configured on the site — set one in Site Config > Site Features. — The site has no Payment page.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No Payment page is configured on the site — set one in Site Config > Site Features.","path":"/storefront/payments/charge-link/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Charge not found — No transaction with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Charge not found","path":"/storefront/payments/charge-link/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"description":"Emails and/or texts the charge's payment link (the same URL the QR encodes) with the `payment-request` / `payment-request-sms` templates. Addresses default to the ones given when the charge was created; `channels` defaults to every channel with an address.\n\n#### Signature\n\n```http\nPOST /storefront/payments/charge-link/{id}/send (id: string, body) -> Channels sent and the link\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Anonymous: StorefrontController is `@PublicRoute()` at class level and this handler does not override it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CHARGE_NOT_FOUND | Charge not found | No transaction with that id. | — |\n| `400` | NO_PAYMENT_PAGE | No Payment page is configured on the site — set one in Site Config > Site Features. | The site has no Payment page. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"phone":{"type":"string"},"channels":{"type":"array","items":{"type":"string","enum":["email","sms"]}}}},"example":{"phone":"+15555550123","channels":["sms"]}}}}}},"/storefront/payments/charge-link/{id}/status":{"get":{"operationId":"StorefrontController_chargeLinkStatus","summary":"Has a charge been paid","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Charge id."}],"responses":{"200":{"description":"Payment state","content":{"application/json":{"schema":{"type":"object","properties":{"paid":{"type":"boolean"},"amount":{"type":"number"},"currency":{"type":"string"},"reference":{"type":"string"},"status":{"type":"string","example":"pending"}}}}}},"404":{"description":"Charge not found — No transaction with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Charge not found","path":"/storefront/payments/charge-link/{id}/status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"description":"Polled by the till while the customer pays on their own device. `paid` is true once the transaction status is paid, succeeded or completed.\n\n#### Signature\n\n```http\nGET /storefront/payments/charge-link/{id}/status (id: string) -> Payment state\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CHARGE_NOT_FOUND | Charge not found | No transaction with that id. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/storefront/payment-gateways":{"get":{"operationId":"StorefrontController_listPaymentGateways","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"amount","required":false,"in":"query","schema":{"type":"string"},"description":"With `currency`: create a Stripe PaymentIntent for this amount.","example":"129.99"},{"name":"currency","required":false,"in":"query","schema":{"type":"string"},"description":"With `amount`.","example":"USD"},{"name":"email","required":false,"in":"query","schema":{"type":"string"},"description":"Customer email for the eager Stripe intent.","example":"ada@example.com"}],"responses":{"200":{"description":"The gateways, public fields only","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"sk":{"type":"string"},"name":{"type":"string"},"datatype":{"type":"string","example":"config"},"configured":{"type":"boolean"},"data":{"type":"object","additionalProperties":true,"properties":{"provider":{"type":"string","example":"StripeProvider"},"name":{"type":"string"},"publishableKey":{"type":"string"},"clientId":{"type":"string"},"sandbox":{"type":"boolean"},"testMode":{"type":"boolean"},"instructions":{"type":"string"}}},"payIntent":{"type":"object","description":"Stripe only, when amount + currency were given.","properties":{"id":{"type":"string"},"client_secret":{"type":"string"},"publishableKey":{"type":"string"}}}}}},"example":[{"sk":"66f1…","name":"default","datatype":"config","configured":true,"data":{"provider":"StripeProvider","name":"default","publishableKey":"pk_live_…"},"payIntent":{"id":"pi_3Pab…","client_secret":"pi_3Pab…_secret_…","publishableKey":"pk_live_…"}}]}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"summary":"List available payment gateways","description":"The org's configured, **active** gateways from Stripe, PayPal, Offline, Gift card and Account balance (the `default` config of each), in that order — build the payment selector from this rather than hard-coding options.\n\nWhen `amount` and `currency` are both given, a Stripe PaymentIntent is created up front and attached to the Stripe entry as `payIntent`, so the client can mount Stripe Elements without a second round-trip. `email` is passed to that intent.\n\n**Only the public view of each gateway is returned**: `sk`, `name`, `datatype`, `configured` and `data` limited to presentation and client-SDK fields (`name`, `provider`, `type`, `status`, `displayName`, `description`, `clientId`, `publishableKey`, `publicKey`, `clientToken`, `testMode`, `sandbox`, `default`, `merchantName`, `supportedMethods`, `instructions`, `bankDetails`). Secret keys, API keys and webhook secrets never leave the server. `configured` is false when the gateway is missing a credential it needs to take money (Stripe secret + publishable key, PayPal app secret + client id, Helcim API token).\n\n#### Signature\n\n```http\nGET /storefront/payment-gateways (amount?: string, currency?: string, email?: string) -> The gateways, public fields only\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A gateway that errors while loading is skipped (logged), not returned as an error.\n- Inactive or deprecated gateway configs are never listed.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/take-payment`\n- `GET /storefront/paypal/config`\n- `POST /storefront/stripe/intent`"}},"/storefront/take-payment":{"post":{"operationId":"StorefrontController_takePayment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The payment result, including the reference to pass as `paymentRef`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/take-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Payments"],"summary":"Take a payment","description":"Charges the customer through the configured gateway and returns the payment result. Use the returned reference as `paymentRef` when creating the order.\n\n#### Signature\n\n```http\nPOST /storefront/take-payment (body) -> The payment result, including the reference to pass as `paymentRef`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Not idempotent. A retry charges again — capture the reference from the first response before retrying.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/checkout-cart`\n- `GET /storefront/payment-gateways`","requestBody":{"description":"Payment details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Amount to charge.","example":129.99},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","description":"Which configured gateway to charge through.","example":"stripe"},"paymentMethod":{"type":"string","description":"Gateway payment-method token.","example":"pm_1PabcXYZ"},"email":{"type":"string","example":"ada@example.com"},"orderId":{"type":"string","description":"Order this payment belongs to, when it already exists."}}},"example":{"amount":129.99,"currency":"USD","gateway":"stripe","paymentMethod":"pm_1PabcXYZ","email":"ada@example.com"}}}}}},"/storefront/checkout-cart":{"post":{"operationId":"StorefrontController_checkoutCart","summary":"Check out a cart","description":"Converts a cart into an order: creates the `sf_order`, records the payment reference, and triggers the order-confirmation notification.\n\nThis is the standard product-only checkout. Use `checkout-mixed` when the basket contains rentals, and `checkout-buy-now` to skip the cart entirely.\n\n#### Signature\n\n```http\nPOST /storefront/checkout-cart (body) -> The created order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Payment is expected to have been taken already — pass its reference in `paymentRef`. Take the payment with `POST /storefront/take-payment` or a Stripe intent first.\n- Not idempotent: calling it twice creates two orders. Guard against double submission on the client.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/checkout-mixed`\n- `POST /storefront/take-payment`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Checkout details — contents, addresses, and the payment already taken.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"image":{"type":"string"},"options":{"type":"string","description":"Selected variant options.","example":"size:330ml"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}},"description":"Lines to order."},"cartId":{"type":"string","description":"Cart being checked out.","example":"cart_9f21"},"email":{"type":"string","description":"Customer email. Where the confirmation is sent.","example":"ada@example.com"},"name":{"type":"string","example":"Ada Lovelace"},"phone":{"type":"string","example":"+442071234567"},"shippingAddress":{"type":"object","description":"A postal address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"line1":{"type":"string","example":"12 Ada Way"},"line2":{"type":"string"},"city":{"type":"string","example":"London"},"state":{"type":"string"},"postcode":{"type":"string","example":"E1 6AN"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"GB"},"phone":{"type":"string","example":"+442071234567"}}},"billingAddress":{"type":"object","description":"A postal address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"line1":{"type":"string","example":"12 Ada Way"},"line2":{"type":"string"},"city":{"type":"string","example":"London"},"state":{"type":"string"},"postcode":{"type":"string","example":"E1 6AN"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"GB"},"phone":{"type":"string","example":"+442071234567"}}},"shippingMethod":{"type":"string","example":"ground"},"shippingFee":{"type":"number","example":4.99},"tax":{"type":"number","example":1.92},"discounts":{"type":"array","items":{"type":"object","additionalProperties":true}},"total":{"type":"number","example":30.91},"currency":{"type":"string","example":"USD"},"paymentRef":{"type":"string","description":"Reference for the payment already captured.","example":"pi_3PabcXYZ"},"paymentGateway":{"type":"string","example":"stripe"}}},"example":{"items":[{"sku":"DRK-COLA-330","quantity":2,"price":12}],"email":"ada@example.com","shippingAddress":{"name":"Ada Lovelace","line1":"12 Ada Way","city":"London","postcode":"E1 6AN","country":"GB"},"total":30.91,"currency":"USD","paymentRef":"pi_3PabcXYZ","paymentGateway":"stripe"}}}},"responses":{"201":{"description":"The created order","content":{"application/json":{"schema":{"type":"object","description":"The created order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Checkout"]}},"/storefront/checkout-mixed":{"post":{"operationId":"StorefrontController_checkoutMixed","summary":"Check out a basket of products and rentals","description":"Checks out a basket containing both ordinary products and rental items. The two are routed differently: products become lines on a standard order, while rentals are created as **separate rental bookings** with their own dates and availability rules.\n\nEach line declares which it is via `itemType`. Rental lines additionally need `startDate`, `endDate` and `rentalPeriod`. The response carries both the order and the bookings that were created.\n\n#### Signature\n\n```http\nPOST /storefront/checkout-mixed (body) -> The order and the rental bookings created alongside it\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A line with no `itemType` is treated as a product. Rentals must set it explicitly or they will not be booked.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/checkout-cart`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Mixed basket plus checkout details.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","required":["sku"],"properties":{"sku":{"type":"string","description":"Product or rental SKU.","example":"DRK-COLA-330"},"name":{"type":"string"},"image":{"type":"string"},"options":{"type":"string"},"quantity":{"type":"number","example":1},"price":{"type":"number","example":12},"itemType":{"type":"string","enum":["product","rental"],"description":"Routes the line. `rental` items are booked separately from the order and need the date fields below.","example":"product"},"startDate":{"type":"string","format":"date-time","description":"Rental start. Required for `rental` lines.","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","description":"Rental end. Required for `rental` lines.","example":"2026-09-04T17:00:00.000Z"},"rentalPeriod":{"type":"string","enum":["hourly","daily","weekly","monthly"],"description":"Billing granularity for the rental.","example":"daily"},"optionalFees":{"type":"array","items":{"type":"string"},"description":"Selected optional fees, e.g. insurance."}}},"description":"Products and rentals together. `itemType` routes each line."},"total":{"type":"number","example":189.99},"shippingAddress":{"type":"object","description":"A postal address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"line1":{"type":"string","example":"12 Ada Way"},"line2":{"type":"string"},"city":{"type":"string","example":"London"},"state":{"type":"string"},"postcode":{"type":"string","example":"E1 6AN"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"GB"},"phone":{"type":"string","example":"+442071234567"}}},"billingAddress":{"type":"object","description":"A postal address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"line1":{"type":"string","example":"12 Ada Way"},"line2":{"type":"string"},"city":{"type":"string","example":"London"},"state":{"type":"string"},"postcode":{"type":"string","example":"E1 6AN"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"GB"},"phone":{"type":"string","example":"+442071234567"}}},"paymentRef":{"type":"string","example":"pi_3PabcXYZ"},"paymentGateway":{"type":"string","example":"stripe"},"currency":{"type":"string","example":"USD"},"email":{"type":"string","example":"ada@example.com"},"name":{"type":"string","example":"Ada Lovelace"},"phone":{"type":"string"},"shippingMethod":{"type":"string","example":"ground"},"shippingFee":{"type":"number","example":4.99},"discounts":{"type":"array","items":{"type":"object","additionalProperties":true}},"tax":{"type":"number","example":12}}},"examples":{"mixed":{"summary":"One product and one rental","value":{"items":[{"sku":"DRK-COLA-330","quantity":2,"price":12,"itemType":"product"},{"sku":"CAM-RED-KOMODO","quantity":1,"price":165.99,"itemType":"rental","startDate":"2026-09-01T09:00:00.000Z","endDate":"2026-09-04T17:00:00.000Z","rentalPeriod":"daily"}],"email":"ada@example.com","total":189.99,"currency":"USD","paymentRef":"pi_3PabcXYZ","paymentGateway":"stripe"}}}}}},"responses":{"201":{"description":"The order and the rental bookings created alongside it","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"object","description":"The created order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}},"rentals":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"One booking per rental line."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Checkout"]}},"/storefront/checkout-buy-now":{"post":{"operationId":"StorefrontController_checkoutBuyNow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created order","content":{"application/json":{"schema":{"type":"object","description":"The created order (`sf_order`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Checkout"],"summary":"Buy now, without a cart","description":"Single-item express checkout. Creates the order directly from the item in the body, skipping the cart entirely — the \"Buy it now\" button next to a product.\n\n#### Signature\n\n```http\nPOST /storefront/checkout-buy-now (body) -> The created order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/checkout-cart`","requestBody":{"description":"The item and the checkout details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sku":{"type":"string","description":"Product to buy.","example":"DRK-COLA-330"},"quantity":{"type":"number","default":1,"example":1},"email":{"type":"string","example":"ada@example.com"},"shippingAddress":{"type":"object","description":"A postal address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"line1":{"type":"string","example":"12 Ada Way"},"line2":{"type":"string"},"city":{"type":"string","example":"London"},"state":{"type":"string"},"postcode":{"type":"string","example":"E1 6AN"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"GB"},"phone":{"type":"string","example":"+442071234567"}}},"billingAddress":{"type":"object","description":"A postal address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"line1":{"type":"string","example":"12 Ada Way"},"line2":{"type":"string"},"city":{"type":"string","example":"London"},"state":{"type":"string"},"postcode":{"type":"string","example":"E1 6AN"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"GB"},"phone":{"type":"string","example":"+442071234567"}}},"paymentRef":{"type":"string","example":"pi_3PabcXYZ"},"paymentGateway":{"type":"string","example":"stripe"},"currency":{"type":"string","example":"USD"}}},"example":{"sku":"DRK-COLA-330","quantity":1,"email":"ada@example.com","paymentRef":"pi_3PabcXYZ","paymentGateway":"stripe"}}}}}},"/storefront/update-subscription":{"post":{"operationId":"StorefrontController_subscriptionUpdate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated subscription","content":{"application/json":{"schema":{"type":"object","description":"A subscription.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"The payment gateway rejected the request. — Stripe returned an error — an invalid amount, a missing configuration, or a declined card.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The payment gateway rejected the request.","path":"/storefront/update-subscription","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Subscription not found — `subscriptionId` does not resolve in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Subscription not found","path":"/storefront/update-subscription","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Subscriptions"],"summary":"Update a subscription","description":"Changes a subscription — plan, quantity, or cancellation. The change is applied both locally and at the payment gateway, so billing follows the new terms.\n\n#### Signature\n\n```http\nPOST /storefront/update-subscription (body) -> The updated subscription\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SUBSCRIPTION_NOT_FOUND | Subscription not found | `subscriptionId` does not resolve in the org. | List the customer's subscriptions to find the right identifier. |\n| `400` | GATEWAY_ERROR | The payment gateway rejected the request. | Stripe returned an error — an invalid amount, a missing configuration, or a declined card. | The gateway message is passed through. Fix the reported problem and retry; do not retry an unchanged declined request. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/subscriptions/get/{author}/{subscriptionid}`","requestBody":{"description":"The subscription and the change to apply.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subscriptionId":{"type":"string","description":"Subscription to change.","example":"SUB-4821"},"plan":{"type":"string","description":"New plan name.","example":"pro-annual"},"quantity":{"type":"number","description":"New seat or unit count.","example":5},"cancel":{"type":"boolean","description":"Cancel the subscription.","example":false}}},"examples":{"changePlan":{"summary":"Move to a different plan","value":{"subscriptionId":"SUB-4821","plan":"pro-annual"}},"changeSeats":{"summary":"Change seat count","value":{"subscriptionId":"SUB-4821","quantity":5}},"cancel":{"summary":"Cancel","value":{"subscriptionId":"SUB-4821","cancel":true}}}}}}}},"/storefront/sync/partners":{"get":{"operationId":"StorefrontController_getShoppingPartners","summary":"List shopping partners","description":"Returns the marketplace and shopping partners available for product sync, with each one's connection state for this org. Use it to build a sync UI rather than hard-coding the partner list.\n\n#### Signature\n\n```http\nGET /storefront/sync/partners (configId?: string) -> The available partners and their connection state\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/{partner}/{productId}`\n- `POST /storefront/sync/add-products`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"configId","required":false,"in":"query","schema":{"type":"string","default":"default"},"description":"Which configured connection to the partner to use. An org can hold several connections to one partner — separate Google Merchant accounts, for instance. Omit to use the connection named `default`.","example":"default"}],"responses":{"200":{"description":"The available partners and their connection state","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Partner identifier to pass as `partner`.","example":"google"},"name":{"type":"string","example":"Google Merchant Center"},"connected":{"type":"boolean","description":"Whether this org has a working connection.","example":true},"configId":{"type":"string","example":"default"}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"]}},"/storefront/sync/{partner}/{productId}":{"post":{"operationId":"StorefrontController_syncProduct","summary":"Sync one product to a partner","description":"Pushes a single product to one marketplace, creating or updating its listing. This is the call to make right after a product is edited, so the marketplace copy does not drift.\n\n#### Signature\n\n```http\nPOST /storefront/sync/{partner}/{productId} (partner: string, productId: string, configId?: string) -> The sync result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Safe to repeat — a product already listed is updated rather than duplicated.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRODUCT_NOT_FOUND | Product not found | The product id or SK does not resolve in the org. | Check the identifier with `GET /storefront/products`. |\n| `400` | PARTNER_API_ERROR | The partner rejected the request. | The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential. | The partner's message is passed through. Fix the reported problem, or re-authorise the connection if the credential expired. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/all/{productId}`\n- `DELETE /storefront/sync/{partner}/{productId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"partner","required":true,"in":"path","description":"Partner identifier, e.g. `google`, `facebook`, `tiktok`, `amazon`, `shopify`. List the supported values with `GET /storefront/sync/partners`.","schema":{"type":"string"},"example":"google"},{"name":"productId","required":true,"in":"path","description":"Product `sk` or id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"configId","required":false,"in":"query","schema":{"type":"string","default":"default"},"description":"Which configured connection to the partner to use. An org can hold several connections to one partner — separate Google Merchant accounts, for instance. Omit to use the connection named `default`.","example":"default"}],"responses":{"201":{"description":"The sync result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Outcome of the sync, per product.","properties":{"success":{"type":"boolean","example":true},"partner":{"type":"string","example":"google"},"results":{"type":"array","description":"One entry per product. Individual failures are reported here rather than failing the whole request.","items":{"type":"object","properties":{"productId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"},"sku":{"type":"string","example":"DRK-COLA-330"},"status":{"type":"string","description":"`synced`, `failed`, or `skipped`.","example":"synced"},"message":{"type":"string","description":"Partner-reported detail when the line did not succeed."}}}}}}}}},"400":{"description":"The partner rejected the request. — The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The partner rejected the request.","path":"/storefront/sync/{partner}/{productId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Product not found — The product id or SK does not resolve in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Product not found","path":"/storefront/sync/{partner}/{productId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"]},"delete":{"operationId":"StorefrontController_deleteProduct","summary":"Remove a product from a partner","description":"Delists a single product from one marketplace. The product itself is untouched in your catalog — only the partner's copy is removed.\n\n#### Signature\n\n```http\nDELETE /storefront/sync/{partner}/{productId} (partner: string, productId: string, configId?: string) -> The delist result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Deleting a product that is not listed is not treated as an error by most partners.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PARTNER_NOT_CONFIGURED | Partner integration not found for this organization | The org has no connection to that partner, or none under the given `configId`. | Connect the partner first. `GET /storefront/sync/partners` lists what is configured. |\n| `400` | PARTNER_API_ERROR | The partner rejected the request. | The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential. | The partner's message is passed through. Fix the reported problem, or re-authorise the connection if the credential expired. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/delete-products`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"partner","required":true,"in":"path","description":"Partner identifier, e.g. `google`, `facebook`, `tiktok`, `amazon`, `shopify`. List the supported values with `GET /storefront/sync/partners`.","schema":{"type":"string"},"example":"google"},{"name":"productId","required":true,"in":"path","description":"Product id as known to the partner, or the local `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"configId","required":false,"in":"query","schema":{"type":"string","default":"default"},"description":"Which configured connection to the partner to use. An org can hold several connections to one partner — separate Google Merchant accounts, for instance. Omit to use the connection named `default`.","example":"default"}],"responses":{"200":{"description":"The delist result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Outcome of the sync, per product.","properties":{"success":{"type":"boolean","example":true},"partner":{"type":"string","example":"google"},"results":{"type":"array","description":"One entry per product. Individual failures are reported here rather than failing the whole request.","items":{"type":"object","properties":{"productId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"},"sku":{"type":"string","example":"DRK-COLA-330"},"status":{"type":"string","description":"`synced`, `failed`, or `skipped`.","example":"synced"},"message":{"type":"string","description":"Partner-reported detail when the line did not succeed."}}}}}}}}},"400":{"description":"The partner rejected the request. — The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The partner rejected the request.","path":"/storefront/sync/{partner}/{productId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Partner integration not found for this organization — The org has no connection to that partner, or none under the given `configId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Partner integration not found for this organization","path":"/storefront/sync/{partner}/{productId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"]}},"/storefront/sync/all/{productId}":{"post":{"operationId":"StorefrontController_syncProductToAll","summary":"Sync one product to every partner","description":"Pushes a product to every partner the org has configured, in one call. Partners are attempted independently — one marketplace rejecting the product does not stop the others.\n\n#### Signature\n\n```http\nPOST /storefront/sync/all/{productId} (productId: string) -> One result per configured partner\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Check every entry in the response: a `200` here means the request ran, not that every partner accepted the product.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRODUCT_NOT_FOUND | Product not found | The product id or SK does not resolve in the org. | Check the identifier with `GET /storefront/products`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/{partner}/{productId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"productId","required":true,"in":"path","description":"Product `sk` or id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"One result per configured partner","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"Outcome of the sync, per product.","properties":{"success":{"type":"boolean","example":true},"partner":{"type":"string","example":"google"},"results":{"type":"array","description":"One entry per product. Individual failures are reported here rather than failing the whole request.","items":{"type":"object","properties":{"productId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"},"sku":{"type":"string","example":"DRK-COLA-330"},"status":{"type":"string","description":"`synced`, `failed`, or `skipped`.","example":"synced"},"message":{"type":"string","description":"Partner-reported detail when the line did not succeed."}}}}}}}}}},"404":{"description":"Product not found — The product id or SK does not resolve in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Product not found","path":"/storefront/sync/all/{productId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"]}},"/storefront/sync/add-products":{"post":{"operationId":"StorefrontController_syncServiceAdd","summary":"Sync several products to a partner","description":"Bulk version of the single-product sync: pushes many products to one marketplace in a single call.\n\nSet `alsoPostToSocial` to publish a social post for the same products as part of the operation, configured through `postOptions`. That is a convenience over calling `sync/post-to-social` separately, and it uses the same rendering.\n\n#### Signature\n\n```http\nPOST /storefront/sync/add-products (body) -> Per-product sync results, plus the social post result when one was requested\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PARTNER_NOT_CONFIGURED | Partner integration not found for this organization | The org has no connection to that partner, or none under the given `configId`. | Connect the partner first. `GET /storefront/sync/partners` lists what is configured. |\n| `400` | PARTNER_API_ERROR | The partner rejected the request. | The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential. | The partner's message is passed through. Fix the reported problem, or re-authorise the connection if the credential expired. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/delete-products`\n- `POST /storefront/sync/post-to-social`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which products to push where.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["partner","productIds"],"properties":{"partner":{"type":"string","description":"Partner identifier.","example":"google"},"productIds":{"type":"array","items":{"type":"string"},"description":"Product `sk` values to sync.","example":["66f1a2b3c4d5e6f708192a3b"]},"configId":{"type":"string","default":"default","description":"Which configured connection to use."},"baseURL":{"type":"string","description":"Base URL for product links. Defaults to the site settings.","example":"https://shop.example.com"},"alsoPostToSocial":{"type":"boolean","default":false,"description":"Also publish a social post for these products.","example":false},"postOptions":{"type":"object","description":"Social post settings. Only read when `alsoPostToSocial` is true.","properties":{"platform":{"type":"string","enum":["facebook","instagram","tiktok","twitter","linkedin","pinterest"]},"postType":{"type":"string","enum":["image","carousel","video","story","reel"]},"customCaption":{"type":"string"},"customHashtags":{"type":"array","items":{"type":"string"}},"includePrice":{"type":"boolean","default":true},"includeHashtags":{"type":"boolean","default":true},"scheduleAt":{"type":"string","format":"date-time","description":"Publish later instead of now."}}}}},"examples":{"bulk":{"summary":"Sync three products to Google","value":{"partner":"google","productIds":["sk_1","sk_2","sk_3"]}},"withSocial":{"summary":"Sync and post to Instagram","value":{"partner":"google","productIds":["sk_1"],"alsoPostToSocial":true,"postOptions":{"platform":"instagram","postType":"image","includePrice":true}}}}}}},"responses":{"201":{"description":"Per-product sync results, plus the social post result when one was requested","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Outcome of the sync, per product.","properties":{"success":{"type":"boolean","example":true},"partner":{"type":"string","example":"google"},"results":{"type":"array","description":"One entry per product. Individual failures are reported here rather than failing the whole request.","items":{"type":"object","properties":{"productId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"},"sku":{"type":"string","example":"DRK-COLA-330"},"status":{"type":"string","description":"`synced`, `failed`, or `skipped`.","example":"synced"},"message":{"type":"string","description":"Partner-reported detail when the line did not succeed."}}}}}}}}},"400":{"description":"The partner rejected the request. — The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The partner rejected the request.","path":"/storefront/sync/add-products","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Partner integration not found for this organization — The org has no connection to that partner, or none under the given `configId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Partner integration not found for this organization","path":"/storefront/sync/add-products","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"]}},"/storefront/sync/delete-products":{"post":{"operationId":"StorefrontController_syncServiceDelete","summary":"Remove several products from a partner","description":"Bulk delist: removes many products from one marketplace catalog in a single call. Your own catalog is unaffected.\n\n#### Signature\n\n```http\nPOST /storefront/sync/delete-products (body) -> Per-product delist results\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PARTNER_NOT_CONFIGURED | Partner integration not found for this organization | The org has no connection to that partner, or none under the given `configId`. | Connect the partner first. `GET /storefront/sync/partners` lists what is configured. |\n| `400` | PARTNER_API_ERROR | The partner rejected the request. | The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential. | The partner's message is passed through. Fix the reported problem, or re-authorise the connection if the credential expired. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /storefront/sync/{partner}/{productId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which products to remove from where.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["partner","productIds"],"properties":{"partner":{"type":"string","description":"Partner identifier.","example":"google"},"productIds":{"type":"array","items":{"type":"string"},"description":"Product ids to delist.","example":["sk_1","sk_2"]},"configId":{"type":"string","default":"default"},"skus":{"type":"array","items":{"type":"string"},"description":"Optional SKUs, for partners that key their catalog on SKU rather than id.","example":["DRK-COLA-330"]}}},"example":{"partner":"google","productIds":["sk_1","sk_2"]}}}},"responses":{"201":{"description":"Per-product delist results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Outcome of the sync, per product.","properties":{"success":{"type":"boolean","example":true},"partner":{"type":"string","example":"google"},"results":{"type":"array","description":"One entry per product. Individual failures are reported here rather than failing the whole request.","items":{"type":"object","properties":{"productId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"},"sku":{"type":"string","example":"DRK-COLA-330"},"status":{"type":"string","description":"`synced`, `failed`, or `skipped`.","example":"synced"},"message":{"type":"string","description":"Partner-reported detail when the line did not succeed."}}}}}}}}},"400":{"description":"The partner rejected the request. — The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The partner rejected the request.","path":"/storefront/sync/delete-products","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Partner integration not found for this organization — The org has no connection to that partner, or none under the given `configId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Partner integration not found for this organization","path":"/storefront/sync/delete-products","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"]}},"/storefront/sync/products":{"get":{"operationId":"StorefrontController_listAllProducts","summary":"List products across all partners","description":"Returns the products currently listed at every configured partner — what the marketplaces think you are selling, which is the thing to compare against your own catalog when reconciling.\n\n**This endpoint takes no parameters.** It accepts neither paging nor a partner filter, and returns whatever each partner's default page size yields. Use `GET /storefront/sync/products/{partner}` when you need `limit`, `pageToken` or `configId`.\n\n#### Signature\n\n```http\nGET /storefront/sync/products () -> Listed products grouped by partner\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unpaged. On a large catalog the result is truncated by each partner's own default page size, with no way to fetch the rest — use the per-partner endpoint for a complete listing.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/sync/products/{partner}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"pageToken","required":false,"in":"query","description":"Page token for pagination","schema":{}},{"name":"limit","required":false,"in":"query","description":"Maximum number of products to return","schema":{}},{"name":"configId","required":false,"in":"query","description":"Config ID (optional)","schema":{}},{"name":"partner","required":false,"in":"path","description":"Partner ID (google, facebook, amazon, shopify, etc.) - optional","schema":{}}],"responses":{"200":{"description":"Listed products grouped by partner","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Keyed by partner id, each holding that partner's listed products."}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"]}},"/storefront/sync/products/{partner}":{"get":{"operationId":"StorefrontController_listPartnerProducts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"partner","required":true,"in":"path","schema":{"type":"string"},"description":"Partner identifier, e.g. `google`, `facebook`, `tiktok`, `amazon`, `shopify`. List the supported values with `GET /storefront/sync/partners`.","example":"google"},{"name":"configId","required":false,"in":"query","schema":{"type":"string","default":"default"},"description":"Which configured connection to the partner to use. An org can hold several connections to one partner — separate Google Merchant accounts, for instance. Omit to use the connection named `default`.","example":"default"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Maximum products to return. The partner's own maximum still applies.","example":100},{"name":"pageToken","required":false,"in":"query","schema":{"type":"string"},"description":"Opaque cursor from the previous response. Omit for the first page."}],"responses":{"200":{"description":"A page of listed products, with a token for the next page","content":{"application/json":{"schema":{"type":"object","properties":{"products":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The partner's listing records, in the partner's own shape."},"nextPageToken":{"type":"string","description":"Pass as `pageToken` to continue. Absent on the last page."}}}}}},"400":{"description":"The partner rejected the request. — The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The partner rejected the request.","path":"/storefront/sync/products/{partner}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Partner integration not found for this organization — The org has no connection to that partner, or none under the given `configId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Partner integration not found for this organization","path":"/storefront/sync/products/{partner}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Sync"],"summary":"List products at one partner","description":"Returns the products currently listed at a single marketplace, with paging. This is the endpoint to use for reconciliation — it is the only listing that can walk a full catalog.\n\nPaging is the partner's own: pass the `pageToken` from the previous response to fetch the next page, and stop when no token comes back.\n\n#### Signature\n\n```http\nGET /storefront/sync/products/{partner} (partner: string, configId?: string, limit?: integer, pageToken?: string) -> A page of listed products, with a token for the next page\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The product shape is the partner's, not the platform's — fields differ between Google, Facebook and Shopify.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PARTNER_NOT_CONFIGURED | Partner integration not found for this organization | The org has no connection to that partner, or none under the given `configId`. | Connect the partner first. `GET /storefront/sync/partners` lists what is configured. |\n| `400` | PARTNER_API_ERROR | The partner rejected the request. | The marketplace API returned an error — a policy violation, a missing required attribute, or an expired credential. | The partner's message is passed through. Fix the reported problem, or re-authorise the connection if the credential expired. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/sync/products`"}},"/storefront/sync/post-to-social":{"post":{"operationId":"StorefrontController_postToSocial","summary":"Post products to social media","description":"Builds a social post from one or more products and publishes it, or schedules it for later.\n\nThe caption is assembled from the products unless `customCaption` overrides it; price, hashtags and a product link are included by default and can each be switched off. `postType` is auto-detected from the product media when omitted.\n\nPreview the result first with `POST /storefront/sync/preview-social-post` — that runs the same rendering without publishing.\n\n#### Signature\n\n```http\nPOST /storefront/sync/post-to-social (body) -> The published or scheduled post\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRODUCT_NOT_FOUND | Product not found | The product id or SK does not resolve in the org. | Check the identifier with `GET /storefront/products`. |\n| `400` | INVALID_TARGET | Invalid or unreachable post target | `to` is malformed for the platform, or names a page the connection cannot publish to. | Use a `to` value from `GET /storefront/sync/social-targets/{platform}`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/preview-social-post`\n- `GET /storefront/sync/social-targets/{platform}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to post, and where.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["platform","productIds","to","siteName"],"properties":{"platform":{"type":"string","enum":["facebook","instagram","tiktok","twitter","linkedin","pinterest"],"description":"Target platform.","example":"instagram"},"productIds":{"type":"array","items":{"type":"string"},"description":"Product `sk` values to feature.","example":["66f1a2b3c4d5e6f708192a3b"]},"to":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Destination, in the platform's own format. Take it from `GET /storefront/sync/social-targets/{platform}` rather than building it.","example":"x/1234567890/17841400000000000"},"siteName":{"type":"string","description":"Site whose store URL is used to build product links. Required even when `includeLink` is false.","example":"main-store"},"postType":{"type":"string","enum":["image","carousel","video","story","reel"],"description":"Auto-detected from the product media when omitted.","example":"carousel"},"customCaption":{"type":"string","description":"Replaces the generated caption entirely.","example":"Summer refresh — now 20% off."},"customHashtags":{"type":"array","items":{"type":"string"},"description":"Hashtags to use instead of the generated ones.","example":["summer","sale"]},"includePrice":{"type":"boolean","default":true,"description":"Include the price in the caption."},"includeHashtags":{"type":"boolean","default":true,"description":"Include hashtags."},"includeLink":{"type":"boolean","default":true,"description":"Include a product link."},"scheduleAt":{"type":"string","format":"date-time","description":"Publish at this time instead of immediately.","example":"2026-09-01T09:00:00.000Z"},"configId":{"type":"string","description":"Which configured integration to publish through."}}},"examples":{"single":{"summary":"Post one product to Instagram now","value":{"platform":"instagram","productIds":["66f1a2b3c4d5e6f708192a3b"],"to":"x/1234567890/17841400000000000","siteName":"main-store"}},"scheduledCarousel":{"summary":"Schedule a carousel with a custom caption","value":{"platform":"instagram","productIds":["sk_1","sk_2","sk_3"],"to":"x/1234567890/17841400000000000","siteName":"main-store","postType":"carousel","customCaption":"Summer refresh — now 20% off.","customHashtags":["summer","sale"],"scheduleAt":"2026-09-01T09:00:00.000Z"}}}}}},"responses":{"201":{"description":"The published or scheduled post","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"success":{"type":"boolean","example":true},"postId":{"type":"string","description":"Platform post id. Absent for a scheduled post that has not published yet."},"scheduledAt":{"type":"string","format":"date-time","description":"Present when the post was scheduled rather than published."}}}}}},"400":{"description":"Invalid or unreachable post target — `to` is malformed for the platform, or names a page the connection cannot publish to.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or unreachable post target","path":"/storefront/sync/post-to-social","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Product not found — The product id or SK does not resolve in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Product not found","path":"/storefront/sync/post-to-social","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Social"]}},"/storefront/sync/preview-social-post":{"post":{"operationId":"StorefrontController_previewSocialPost","summary":"Preview a social post","description":"Renders exactly what `post-to-social` would publish — caption, hashtags, link and selected media — and returns it without posting anything. Nothing is sent to the platform and no integration is required.\n\nIt takes the same body minus `to`, `scheduleAt` and `configId`, since there is no destination involved.\n\n#### Signature\n\n```http\nPOST /storefront/sync/preview-social-post (body) -> The rendered post\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRODUCT_NOT_FOUND | Product not found | The product id or SK does not resolve in the org. | Check the identifier with `GET /storefront/products`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/post-to-social`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The rendered post","content":{"application/json":{"schema":{"type":"object","properties":{"caption":{"type":"string","description":"The full caption as it would be published.","example":"Cola 330ml — $9.60\n\nShop now: https://shop.example.com/p/cola-330ml\n\n#drinks #sale"},"hashtags":{"type":"array","items":{"type":"string"},"example":["drinks","sale"]},"media":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The images or video that would be attached."},"postType":{"type":"string","description":"The resolved post type.","example":"image"}}}}}},"404":{"description":"Product not found — The product id or SK does not resolve in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Product not found","path":"/storefront/sync/preview-social-post","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Social"],"requestBody":{"description":"The post to render.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["platform","productIds","siteName"],"properties":{"platform":{"type":"string","enum":["facebook","instagram","tiktok","twitter","linkedin","pinterest"],"example":"instagram"},"productIds":{"type":"array","items":{"type":"string"},"example":["66f1a2b3c4d5e6f708192a3b"]},"siteName":{"type":"string","description":"Site whose store URL builds the product links.","example":"main-store"},"postType":{"type":"string","enum":["image","carousel","video","story","reel"]},"customCaption":{"type":"string"},"customHashtags":{"type":"array","items":{"type":"string"}},"includePrice":{"type":"boolean","default":true},"includeHashtags":{"type":"boolean","default":true},"includeLink":{"type":"boolean","default":true}}},"example":{"platform":"instagram","productIds":["66f1a2b3c4d5e6f708192a3b"],"siteName":"main-store","includePrice":true}}}}}},"/storefront/sync/social-targets/{platform}":{"get":{"operationId":"StorefrontController_getSocialTargets","summary":"List social post targets","description":"Lists the pages, accounts and boards a product post can be published to on a platform. Each target includes a ready-to-use `to` value, so a client never has to assemble the platform-specific format itself.\n\nCall this to populate a destination picker before posting.\n\n#### Signature\n\n```http\nGET /storefront/sync/social-targets/{platform} (platform: string) -> The available targets\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Always take `to` from this response. Its format differs per platform — Facebook uses `x/pageId`, Instagram `x/pageId/instagramId`, Pinterest a board id, and the rest a bare account id.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PLATFORM_NOT_CONNECTED | No integration configured for this platform | The org has not connected that social platform. | Connect the platform in the org integration settings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/sync/post-to-social`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"platform","required":true,"in":"path","description":"One of `facebook`, `instagram`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","schema":{"type":"string"},"example":"instagram"}],"responses":{"200":{"description":"The available targets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Platform-native identifier.","example":"17841400000000000"},"name":{"type":"string","description":"Human-readable name.","example":"Acme Retail"},"to":{"type":"string","description":"Pass this verbatim as `to` when posting.","example":"x/1234567890/17841400000000000"}}}}}}},"404":{"description":"No integration configured for this platform — The org has not connected that social platform.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No integration configured for this platform","path":"/storefront/sync/social-targets/{platform}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Social"]}},"/storefront/workflows/order-management":{"post":{"operationId":"StorefrontController_createOrderManagementWorkflow","summary":"Create an order management workflow","description":"Creates a standard order-management workflow from the built-in template, giving orders a pipeline with the usual fulfilment stages. Customise the stages afterwards through the workflow API — this endpoint only scaffolds the template.\n\n#### Signature\n\n```http\nPOST /storefront/workflows/order-management (body) -> The created workflow\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | WORKFLOW_EXISTS | A workflow with this name already exists | The org already has a workflow with that name. | Read the existing one with `GET /storefront/workflows/order-management/{name}`, or create it under a different name. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/workflows/order-management/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Workflow name.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","default":"order-management","description":"Unique workflow name. Defaults to `order-management`.","example":"order-management"}}},"examples":{"default":{"summary":"Use the default name","value":{}},"named":{"summary":"Name it explicitly","value":{"name":"wholesale-orders"}}}}}},"responses":{"201":{"description":"The created workflow","content":{"application/json":{"schema":{"type":"object","description":"A workflow definition.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"A workflow with this name already exists — The org already has a workflow with that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A workflow with this name already exists","path":"/storefront/workflows/order-management","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Workflows"]}},"/storefront/workflows/order-management/{name}":{"get":{"operationId":"StorefrontController_getOrderManagementWorkflow","summary":"Get the order management workflow","description":"Fetches an order-management workflow by name. Omit the `name` segment and the workflow is found instead by its owner — the one bound to `sf_order` — which is the reliable way to ask \"what pipeline are orders running on?\" without knowing what it was called.\n\n#### Signature\n\n```http\nGET /storefront/workflows/order-management/{name} (name: string) -> The workflow definition\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKFLOW_NOT_FOUND | Order management workflow not found | No workflow matches the name, or none is bound to `sf_order`. | Create one with `POST /storefront/workflows/order-management`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/workflows/order-management`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Workflow name. Omit to find the workflow bound to `sf_order`.","schema":{"type":"string"},"example":"order-management"}],"responses":{"200":{"description":"The workflow definition","content":{"application/json":{"schema":{"type":"object","description":"A workflow definition.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"404":{"description":"Order management workflow not found — No workflow matches the name, or none is bound to `sf_order`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Order management workflow not found","path":"/storefront/workflows/order-management/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Workflows"]}},"/storefront/giftcards/balance":{"get":{"operationId":"GiftCardController_checkBalance","summary":"Check a gift card balance","description":"Looks a card up by its **redemption code** and reports whether it can be spent and how much is on it. Separators and case in the code are ignored.\n\n**A card that cannot be used is still a `200`.** The response carries `valid: false` with a `status` that says why — `not_found`, `invalid_pin`, `expired`, or the card's own status when it is inactive, suspended or cancelled. Branch on `valid` and `status`, not on the HTTP code.\n\nFor an unusable card `balance` is reported as `0` regardless of what the card holds. An active card found past its date is marked `expired` on the way through.\n\n**Staff only** — the till and admin. Shoppers check a card through `GET /client/giftcards/balance`, which carries the per-address throttle.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/balance (code?: string, pin?: string) -> The card's spendability and balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The PIN is never echoed back and is never stored — only an HMAC of it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | FORBIDDEN | This needs someone signed in to the business | Called with a site's app token or a customer token. | Shoppers use GET /client/giftcards/balance. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/giftcards/redeem`\n- `GET /client/giftcards/balance`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"},"description":"The redemption code. Not the serial.","example":"YMFB-CDZ2-KDXS-KLM9"},{"name":"pin","required":false,"in":"query","schema":{"type":"string"},"description":"Required for physical cards. A wrong or missing PIN yields `status: \"invalid_pin\"`.","example":"4821"}],"responses":{"200":{"description":"The card's spendability and balance","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","example":true},"balance":{"type":"number","description":"Remaining value. **`0` whenever `valid` is false.**","example":35.5},"currency":{"type":"string","example":"USD"},"status":{"type":"string","description":"The card status, or `not_found` / `invalid_pin`.","example":"active"},"expirationDate":{"type":"string","format":"date-time"},"maxUsePerTransaction":{"type":"number","description":"Present on a valid card that carries the restriction, so a checkout can size the tender without another call."},"minPurchase":{"type":"number"},"message":{"type":"string","description":"Shopper-facing explanation. Present only when `valid` is false."}}},"examples":{"valid":{"summary":"Spendable card","value":{"valid":true,"balance":35.5,"currency":"USD","status":"active"}},"expired":{"summary":"Expired card","value":{"valid":false,"balance":0,"currency":"USD","status":"expired","expirationDate":"2026-01-01T00:00:00.000Z","message":"Gift card has expired"}},"badPin":{"summary":"Wrong PIN","value":{"valid":false,"balance":0,"currency":"USD","status":"invalid_pin","message":"Invalid PIN"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This needs someone signed in to the business — Called with a site's app token or a customer token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This needs someone signed in to the business","path":"/storefront/giftcards/balance","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/wallet/status":{"get":{"operationId":"GiftCardController_walletStatus","summary":"Which wallet passes this store can issue","description":"`{ apple, google }` — true when the pass credentials are set on the gift card integration (Payment gateways › Gift card). Staff version of `GET /client/giftcards/wallet/status`.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/wallet/status () -> { apple, google }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/wallet/apple`\n- `GET /storefront/giftcards/wallet/google`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ apple, google }","content":{"application/json":{"schema":{"type":"object","properties":{"apple":{"type":"boolean"},"google":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/wallet/apple":{"get":{"operationId":"GiftCardController_applePass","summary":"Apple Wallet pass for a card","description":"A signed `.pkpass` for the card with this **code** — balance, a QR of the code, expiry — sent as a file download (`Content-Type: application/vnd.apple.pkpass`). Staff only; shoppers use `GET /client/giftcards/wallet/apple`.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/wallet/apple (code?: string) -> The .pkpass file (binary)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `501` | WALLET_NOT_SET_UP | Apple Wallet is not set up for this store | The pass credentials are not configured on the gift card integration. | Check GET …/wallet/status first and only offer the button when true. |\n| `400` | CODE_REQUIRED | A gift card code is needed | No `code`. | — |\n| `404` | GIFT_CARD_NOT_FOUND | Gift card not found | The code does not resolve. (This one is a real 404.) | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/wallet/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"},"description":"The redemption code, not the serial.","example":"YMFB-CDZ2-KDXS-KLM9"}],"responses":{"200":{"description":"The .pkpass file (binary)","content":{"application/json":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"A gift card code is needed — No `code`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A gift card code is needed","path":"/storefront/giftcards/wallet/apple","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Gift card not found — The code does not resolve. (This one is a real 404.)","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Gift card not found","path":"/storefront/giftcards/wallet/apple","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"501":{"description":"Apple Wallet is not set up for this store — The pass credentials are not configured on the gift card integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":501,"error":"Apple Wallet is not set up for this store","path":"/storefront/giftcards/wallet/apple","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/wallet/google":{"get":{"operationId":"GiftCardController_googlePass","summary":"Google Wallet link for a card","description":"The \"Save to Google Wallet\" link for the card with this **code**. Staff only; shoppers use `GET /client/giftcards/wallet/google`.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/wallet/google (code?: string) -> { url }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `501` | WALLET_NOT_SET_UP | Google Wallet is not set up for this store | The pass credentials are not configured on the gift card integration. | Check GET …/wallet/status first and only offer the button when true. |\n| `400` | CODE_REQUIRED | A gift card code is needed | No `code`. | — |\n| `404` | GIFT_CARD_NOT_FOUND | Gift card not found | The code does not resolve. (This one is a real 404.) | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/wallet/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"},"description":"The redemption code, not the serial.","example":"YMFB-CDZ2-KDXS-KLM9"}],"responses":{"200":{"description":"{ url }","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","example":"https://pay.google.com/gp/v/save/eyJhbGciOi…"}}}}}},"400":{"description":"A gift card code is needed — No `code`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A gift card code is needed","path":"/storefront/giftcards/wallet/google","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Gift card not found — The code does not resolve. (This one is a real 404.)","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Gift card not found","path":"/storefront/giftcards/wallet/google","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"501":{"description":"Google Wallet is not set up for this store — The pass credentials are not configured on the gift card integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":501,"error":"Google Wallet is not set up for this store","path":"/storefront/giftcards/wallet/google","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/redeem":{"post":{"operationId":"GiftCardController_redeem","summary":"Redeem a gift card","description":"Spends value from a card against an order, under a per-card lock, and appends the movement to the card's `uses[]`. **Staff only** (the till); a shopper's checkout spends through `POST /client/giftcards/redeem`, which behaves the same.\n\n**The amount is clamped** to the available balance and to the card's `maxUsePerTransaction`. Asking for more is not an error — the card pays what it can and `amountRedeemed` says how much. Always trust `amountRedeemed`, never the amount you asked for, and collect the difference another way.\n\nThe card is re-validated here, so an expired, suspended or inactive card is refused even if your balance check passed earlier. Restrictions are enforced when `context` describes the order.\n\n**Idempotent on a key.** Send an `Idempotency-Key` header (or `idempotencyKey` in the body). A repeat with the same key returns the first result and moves no value — the site's checkout always sends one.\n\nThe response `transactionId` (GCT-…) is the **payment reference**: record the payment with `gateway: \"giftcard\"` and `ref: <transactionId>` and the ledger verifies it against the card before writing it as paid. One reference can back one payment.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/redeem (body) -> What was actually redeemed\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Two concurrent redemptions on one card are serialised; the balance can never be spent twice.\n- `amountRedeemed` is the number your order should trust, never the amount you requested.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card not found | The code does not resolve. | Check the code. This is a `400`, not a `404`. |\n| `409` | LOCKED | The gift card is being updated by another request. Try again. | Another redemption on the same card held the lock for longer than the retry window (~3s). | Retry with the same idempotency key. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/balance`\n- `POST /storefront/giftcards/{serial}/refund`\n- `POST /storefront/take-payment`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. Same key → same result, no second spend.","schema":{"type":"string"}}],"responses":{"201":{"description":"What was actually redeemed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"amountRedeemed":{"type":"number","description":"**What was actually taken** — may be less than requested.","example":14.5},"remainingBalance":{"type":"number","example":21},"transactionId":{"type":"string","description":"The payment reference, prefixed `GCT-`.","example":"GCT-VB8C-SZ7F-E33V"},"serial":{"type":"string","example":"GC-7K2M-9QX4-H1P7"},"currency":{"type":"string","example":"USD"},"idempotent":{"type":"boolean","description":"Present and true when this response is a replay of an earlier one with the same key."}}},"example":{"success":true,"amountRedeemed":14.5,"remainingBalance":21,"transactionId":"GCT-VB8C-SZ7F-E33V","serial":"GC-7K2M-9QX4-H1P7","currency":"USD"}}}},"400":{"description":"Gift card not found — The code does not resolve.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card not found","path":"/storefront/giftcards/redeem","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The gift card is being updated by another request. Try again. — Another redemption on the same card held the lock for longer than the retry window (~3s).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"The gift card is being updated by another request. Try again.","path":"/storefront/giftcards/redeem","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"The card, the amount and the order.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","amount","orderNumber"],"properties":{"code":{"type":"string","description":"Redemption code, not the serial.","example":"YMFB-CDZ2-KDXS-KLM9"},"amount":{"type":"number","description":"Amount to spend. Clamped to the balance and the per-transaction cap.","example":14.5},"orderNumber":{"type":"string","description":"What the redemption is recorded against — an order, invoice or tab number, or the cart id before an order exists.","example":"A7K2M9QX4"},"pin":{"type":"string","description":"Required for physical cards."},"idempotencyKey":{"type":"string","description":"Alternative to the header."},"context":{"type":"object","description":"What the card is paying for, so its restrictions can be enforced.","properties":{"orderTotal":{"type":"number"},"skus":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"hasDiscountedItems":{"type":"boolean"},"channel":{"type":"string","example":"checkout"}}}}},"examples":{"partial":{"summary":"Spend part of the balance","value":{"code":"YMFB-CDZ2-KDXS-KLM9","amount":14.5,"orderNumber":"A7K2M9QX4","idempotencyKey":"9f1c-…"}},"overAsk":{"summary":"Ask for more than the card holds","description":"Succeeds, redeeming only the balance. Check `amountRedeemed`.","value":{"code":"YMFB-CDZ2-KDXS-KLM9","amount":200,"orderNumber":"A7K2M9QX4"}}}}}}}},"/storefront/giftcards":{"post":{"operationId":"GiftCardController_create","summary":"Create a gift card","description":"Issues a single card. The serial and code are generated by the server; a caller may bring a `code` (a printed stock run) and it is checked for a clash.\n\nCards are `inactive` unless `activate: true` is sent — activate physical stock when it is sold. A physical card gets a PIN, which is **returned once on this response as `data.pin` and never again**; only its hash is stored.\n\n**Issuing does not send anything.** To email/text the card to its recipient, call `POST /storefront/giftcards/{serial}/send` next.\n\nPick `type` by where the value came from: `digital`/`physical` for a card someone paid for (a liability), `promotional` for a free card — a goodwill or apology card, a giveaway — which is not a liability.\n\n**Expiry follows the org policy** (`GET /storefront/giftcards/policy`) for purchased cards: no `expirationDate` takes the policy default (none = never expires), and a date sooner than the policy minimum is refused. Promotional cards may expire whenever.\n\n**Cards sold as products are not issued here.** A product with `giftCard.enabled` on `sf_product` is a gift card: the product page asks for the value (`denominations[]`, or a custom amount within `minAmount`–`maxAmount` when `allowCustomAmount`), a recipient, a message and a delivery date, carried on the cart line as the options \"Gift card amount\", \"Recipient name\", \"Recipient email\", \"Gift message\", \"Deliver on\". Pricing honours \"Gift card amount\" and applies no tier, price list, benefit or discount to the line. When the order is paid — cart checkout, a pay link, or a POS settle — one active digital card is issued per unit (idempotent per order, line and unit), `purchaseOrderNumber` ties it to the order, and it is emailed at once or on the chosen date.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards (body) -> The created card, with its serial and code (and `pin`, once, for a physical card)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_VALUE | A gift card needs a value greater than zero | `initialAmount` is missing or not positive. | Send the value. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/giftcards/batch`\n- `PUT /storefront/giftcards/{serial}/activate`\n- `POST /storefront/giftcards/{serial}/send`\n- `GET /storefront/giftcards/policy`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created card, with its serial and code (and `pin`, once, for a physical card)","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"A gift card needs a value greater than zero — `initialAmount` is missing or not positive.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A gift card needs a value greater than zero","path":"/storefront/giftcards","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"The card to issue.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}},"activate":{"type":"boolean","description":"Issue the card ready to spend."},"pin":{"type":"string","description":"Physical cards only: 4–8 digits. Generated when omitted."}}},"example":{"initialAmount":50,"currency":"USD","type":"digital","activate":true,"recipientEmail":"ada@example.com","recipientName":"Ada"}}}}},"get":{"operationId":"GiftCardController_list","summary":"List gift cards","description":"Operator list with **server-side** search, filters, sort and paging. `search` matches serial, code, recipient name/email, buyer, the selling order and any order the card was spent on. Full records including balances and use history — an operator read.\n\n#### Signature\n\n```http\nGET /storefront/giftcards (search?: string, status?: string, type?: string, batch?: string, campaign?: string, customerId?: string, expiringWithinDays?: integer, sort?: string, sortType?: string, page?: integer, pageSize?: integer) -> A page of gift cards\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/stats`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"]}},{"name":"type","required":false,"in":"query","schema":{"type":"string","enum":["physical","digital","promotional"]}},{"name":"batch","required":false,"in":"query","schema":{"type":"string"}},{"name":"campaign","required":false,"in":"query","schema":{"type":"string"},"description":"Promotional campaign name."},{"name":"search","required":false,"in":"query","schema":{"type":"string"},"example":"ada@example.com"},{"name":"customerId","required":false,"in":"query","schema":{"type":"string"}},{"name":"expiringWithinDays","required":false,"in":"query","schema":{"type":"integer"},"description":"Live cards whose date falls within this many days.","example":30},{"name":"sort","required":false,"in":"query","schema":{"type":"string","default":"createdate"},"description":"createdate, modifydate, data.balance, data.initialAmount, data.expirationDate, data.status, data.type, data.serial, data.recipientEmail"},{"name":"sortType","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":50},"description":"Max 200."}],"responses":{"200":{"description":"A page of gift cards","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/batch":{"post":{"operationId":"GiftCardController_createBatch","summary":"Create a batch of gift cards","description":"Issues `count` identical cards (1–1000) in one call, each with its own serial and code, under a shared `batch` id so they can be listed and managed together. Physical cards come back with their PINs, once, for the printer.\n\nThis is how physical stock and promotional runs are produced. Filter them later with `GET /storefront/giftcards?batch=…`.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/batch (body) -> The issued cards and their batch identifier\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The batch is written synchronously, one card at a time; 1000 is the ceiling per call.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BATCH_SIZE | A batch is between 1 and 1000 cards | `count` is below 1 or above 1000. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards`\n- `GET /storefront/giftcards/batches`\n- `POST /storefront/giftcards/import`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The issued cards and their batch identifier","content":{"application/json":{"schema":{"type":"object","properties":{"batch":{"type":"string"},"count":{"type":"number"},"cards":{"type":"array","items":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}}}}},"400":{"description":"A batch is between 1 and 1000 cards — `count` is below 1 or above 1000.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A batch is between 1 and 1000 cards","path":"/storefront/giftcards/batch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"How many cards, and what they are worth.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["count","amount"],"properties":{"count":{"type":"number","example":100},"amount":{"type":"number","description":"Value of each card.","example":25},"currency":{"type":"string","example":"USD"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"physical"},"name":{"type":"string","description":"Card name; each gets \" n/count\" appended."},"expirationDate":{"type":"string","format":"date-time"},"restrictions":{"type":"object","additionalProperties":true},"activate":{"type":"boolean","description":"Issue the run ready to spend (promotional cards); physical stock is normally activated at the till."}}},"example":{"count":100,"amount":25,"currency":"USD","type":"physical"}}}}}},"/storefront/giftcards/stats":{"get":{"operationId":"GiftCardController_getStats","summary":"Gift card statistics","description":"The finance view, computed in the database: how much value is still owed to customers (`liability`), what was issued and redeemed, what expired unspent (`breakage`), what is about to expire, and the last 30 days. Promotional cards are reported separately and are never counted as a liability.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/stats () -> Aggregate gift card statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Declared before `GET /storefront/giftcards/{serial}`, so `stats` always resolves as this route.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Aggregate gift card statistics","content":{"application/json":{"schema":{"type":"object","properties":{"totalCards":{"type":"integer"},"activeCards":{"type":"integer"},"byStatus":{"type":"object","additionalProperties":{"type":"integer"}},"totalIssued":{"type":"number","description":"Purchased cards only."},"totalRedeemed":{"type":"number"},"totalBalance":{"type":"number","description":"Same as `liability`."},"liability":{"type":"number","description":"Outstanding value on live purchased cards."},"breakage":{"type":"object","properties":{"cards":{"type":"integer"},"value":{"type":"number"}}},"promotional":{"type":"object","properties":{"cards":{"type":"integer"},"issued":{"type":"number"},"outstanding":{"type":"number"}}},"expiringSoon":{"type":"object","properties":{"days":{"type":"integer"},"cards":{"type":"integer"},"value":{"type":"number"}}},"last30Days":{"type":"object","properties":{"issued":{"type":"object"},"redeemed":{"type":"object"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/verify/{transactionId}":{"get":{"operationId":"GiftCardController_verify","summary":"Verify a redemption reference","description":"Whether a GCT-… reference is a real redemption, for how much, on which card. This is what the transaction ledger asks before it writes a `giftcard` payment as paid; exposed so an operator can chase a reference by hand.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/verify/{transactionId} (transactionId: string) -> The verification — always a `200`; read `isValid`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/giftcards/redeem`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"transactionId","required":true,"in":"path","schema":{"type":"string"},"description":"The GCT-… reference from a redemption.","example":"GCT-VB8C-SZ7F-E33V"}],"responses":{"200":{"description":"The verification — always a `200`; read `isValid`","content":{"application/json":{"schema":{"type":"object","properties":{"isValid":{"type":"boolean"},"transactionId":{"type":"string"},"amountUsed":{"type":"number"},"currency":{"type":"string"},"cardNumber":{"type":"string","description":"The card serial."},"orderNumber":{"type":"string"},"usedBy":{"type":"string"},"date":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["completed","not_found"]},"message":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/expire-due":{"post":{"operationId":"GiftCardController_expireDue","summary":"Expire due cards","description":"Flips every live card whose expiration date has passed to `expired` and posts the value left on purchased cards to the ledger as breakage income. A nightly job runs this per org (03:20); calling it is safe at any time and posts nothing twice.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/expire-due () -> What the sweep did\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What the sweep did","content":{"application/json":{"schema":{"type":"object","properties":{"expired":{"type":"integer"},"breakage":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/import":{"post":{"operationId":"GiftCardController_importCards","summary":"Issue cards from a list","description":"One card per row — a customer list, an apology run, a promotion. Rows share the type, currency, expiry, message template and campaign given at the top level, and all land in one new batch (`BATCH-…`). With `send: true` each activated card is emailed and/or texted to its row.\n\n**Bad rows are reported, not fatal**: a row without a positive amount, with a malformed email, or (when sending) with neither email nor phone comes back as `ok: false` with the `problem`, and the rest are issued. Cards are activated unless `activate: false`.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/import (body) -> The batch and one result per row\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMPTY_LIST | The list has no rows | `rows` is missing or empty. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/batches`\n- `POST /storefront/giftcards/{serial}/send`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The batch and one result per row","content":{"application/json":{"schema":{"type":"object","properties":{"batch":{"type":"string"},"issued":{"type":"integer"},"failed":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer"},"ok":{"type":"boolean"},"serial":{"type":"string"},"to":{"type":"string"},"sent":{"type":"array","items":{"type":"string","enum":["email","sms"]}},"problem":{"type":"string"}}}}}},"example":{"batch":"BATCH-7K2M","issued":1,"failed":1,"results":[{"row":1,"ok":true,"serial":"GC-7K2M-9QX4-H1P7","to":"Ada Lovelace","sent":["email"]},{"row":2,"ok":false,"problem":"Amount must be greater than zero"}]}}}},"400":{"description":"The list has no rows — `rows` is missing or empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The list has no rows","path":"/storefront/giftcards/import","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","description":"At most 1000.","items":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"amount":{"type":"number","description":"Currency symbols and separators are stripped (\"$25.00\" works)."},"message":{"type":"string"}}}},"currency":{"type":"string","example":"USD"},"type":{"type":"string","enum":["physical","digital","promotional"],"default":"digital"},"expirationDate":{"type":"string","format":"date-time"},"messageTemplate":{"type":"string"},"campaign":{"type":"string"},"batchName":{"type":"string"},"activate":{"type":"boolean","default":true},"send":{"type":"boolean","description":"Deliver each activated card to its row's email/phone."}}},"example":{"type":"promotional","campaign":"Spring apology","send":true,"rows":[{"name":"Ada Lovelace","email":"ada@example.com","amount":20,"message":"Sorry for the wait!"},{"name":"Grace","phone":"+15555550123","amount":"$15"}]}}}}}},"/storefront/giftcards/reconciliation":{"get":{"operationId":"GiftCardController_reconciliation","summary":"Reconcile gift cards with the books","description":"Compares the ledger's gift card liability account (the mapped `giftCardLiability` role, account 2030 by default) with the sum of balances on live purchased cards (active, inactive, suspended; promotional cards excluded).\n\nHow the books treat cards: the value of cards **sold** is booked to the liability account, not to sales; a payment made with a card debits that liability instead of cash; cards that expire unspent are released as breakage income by the nightly sweep. Promotional cards touch none of this.\n\n`issuedWithoutSale` lists purchased cards issued by hand with no order behind them — their value was never booked as owed, which is the usual cause of a difference; `unexplained` is what remains once those are accounted for. `status` is `balanced`, `out_of_balance`, or `no_account` when the liability account does not exist, and `message` explains it in words.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/reconciliation () -> The comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/stats`\n- `GET /storefront/giftcards/insights`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The comparison","content":{"application/json":{"schema":{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"cards":{"type":"object","properties":{"count":{"type":"integer"},"outstanding":{"type":"number"}}},"ledger":{"type":"object","properties":{"accountCode":{"type":"string","example":"2030"},"accountName":{"type":"string","nullable":true},"found":{"type":"boolean"},"balance":{"type":"number","nullable":true}}},"difference":{"type":"number","nullable":true,"description":"Ledger balance minus card balances; null with no account."},"issuedWithoutSale":{"type":"object","properties":{"cards":{"type":"integer"},"issued":{"type":"number"},"serials":{"type":"array","items":{"type":"string"},"description":"Up to 20."}}},"unexplained":{"type":"number","nullable":true},"status":{"type":"string","enum":["balanced","out_of_balance","no_account"]},"message":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/unusual":{"get":{"operationId":"GiftCardController_unusual","summary":"Unusual gift card redemptions","description":"Redemptions for someone to check, over the last `days`: a card **emptied within an hour** of being issued or activated (the usual shape of a stolen or leaked code), and a card **redeemed five or more times within a day**. Newest first, at most 100 flags.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/unusual (days?: integer) -> The flags\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"days","required":false,"in":"query","schema":{"type":"integer","default":30},"description":"1–365."}],"responses":{"200":{"description":"The flags","content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer"},"count":{"type":"integer"},"flags":{"type":"array","items":{"type":"object","properties":{"serial":{"type":"string"},"holder":{"type":"string","nullable":true},"reason":{"type":"string","example":"Emptied within an hour of being issued"},"at":{"type":"string","format":"date-time"},"amount":{"type":"number"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/insights":{"get":{"operationId":"GiftCardController_insights","summary":"Gift card insights","description":"The gift card dashboard over the last `months`: liability aging (0–30 days … over 2 years), outstanding by batch (top 10), redemption by channel, monthly issued vs redeemed/refunded/reloaded, purchased vs promotional totals, breakage (expired value) for each, and promotional campaigns with their redemption rate.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/insights (months?: integer) -> The dashboard figures\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/stats`\n- `GET /storefront/giftcards/reconciliation`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"months","required":false,"in":"query","schema":{"type":"integer","default":12},"description":"1–36."}],"responses":{"200":{"description":"The dashboard figures","content":{"application/json":{"schema":{"type":"object","properties":{"months":{"type":"integer"},"liabilityAging":{"type":"array","items":{"type":"object","properties":{"bucket":{"type":"string","example":"0–30 days"},"cards":{"type":"integer"},"outstanding":{"type":"number"}}}},"outstandingByBatch":{"type":"array","items":{"type":"object","properties":{"batch":{"type":"string"},"name":{"type":"string","nullable":true},"cards":{"type":"integer"},"outstanding":{"type":"number"}}}},"redemptionByChannel":{"type":"array","items":{"type":"object","properties":{"channel":{"type":"string"},"count":{"type":"integer"},"amount":{"type":"number"}}}},"monthly":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string","example":"2026-09"},"issuedCount":{"type":"integer"},"issued":{"type":"number"},"redeemed":{"type":"number"},"refunded":{"type":"number"},"reloaded":{"type":"number"}}}},"purchased":{"type":"object","properties":{"cards":{"type":"integer"},"issued":{"type":"number"},"redeemed":{"type":"number"},"outstanding":{"type":"number"}}},"promotional":{"type":"object","properties":{"cards":{"type":"integer"},"issued":{"type":"number"},"redeemed":{"type":"number"},"outstanding":{"type":"number"}}},"breakage":{"type":"object","properties":{"purchased":{"type":"object","properties":{"cards":{"type":"integer"},"value":{"type":"number"}}},"promotional":{"type":"object","properties":{"cards":{"type":"integer"},"value":{"type":"number"}}}}},"byCampaign":{"type":"array","items":{"type":"object","properties":{"campaign":{"type":"string"},"cards":{"type":"integer"},"issued":{"type":"number"},"redeemed":{"type":"number"},"outstanding":{"type":"number"},"used":{"type":"integer"},"redemptionRate":{"type":"number","description":"Percent."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/batches":{"get":{"operationId":"GiftCardController_listBatches","summary":"List gift card batches","description":"Each batch id with its name, note, type, size, total value, balance left and cards by status — computed from the cards, newest first. `search` matches the batch id, card name or batch name.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/batches (search?: string, page?: integer, pageSize?: integer) -> A page of batches\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/batches/{batch}/cards`\n- `POST /storefront/giftcards/batches/{batch}/{action}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":25},"description":"Max 100."}],"responses":{"200":{"description":"A page of batches","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"batch":{"type":"string"},"name":{"type":"string"},"note":{"type":"string"},"type":{"type":"string","enum":["physical","digital","promotional"]},"currency":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"count":{"type":"integer"},"value":{"type":"number"},"balance":{"type":"number"},"byStatus":{"type":"object","properties":{"inactive":{"type":"integer"},"active":{"type":"integer"},"used":{"type":"integer"},"expired":{"type":"integer"},"cancelled":{"type":"integer"},"suspended":{"type":"integer"}}}}}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/policy":{"get":{"operationId":"GiftCardController_getPolicy","summary":"Get the gift card expiry policy","description":"How long a **purchased** card lasts when no date is given (`defaultExpiresAfterDays`, null = never expires) and the earliest date an operator may set (`minimumExpiryDays`, default 1825 days / 5 years). Promotional cards are not bound by it.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/policy () -> The policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/giftcards/policy`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The policy","content":{"application/json":{"schema":{"type":"object","properties":{"defaultExpiresAfterDays":{"type":"integer","nullable":true,"description":"Days until a purchased card expires when no date is given; null = never."},"minimumExpiryDays":{"type":"integer","description":"Earliest allowed expiry for a purchased card, in days from issue.","example":1825}}},"example":{"defaultExpiresAfterDays":null,"minimumExpiryDays":1825}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]},"post":{"operationId":"GiftCardController_setPolicy","summary":"Set the gift card expiry policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The policy now in force","content":{"application/json":{"schema":{"type":"object","properties":{"defaultExpiresAfterDays":{"type":"integer","nullable":true,"description":"Days until a purchased card expires when no date is given; null = never."},"minimumExpiryDays":{"type":"integer","description":"Earliest allowed expiry for a purchased card, in days from issue.","example":1825}}}}}},"400":{"description":"The default expiry (<n> days) is shorter than the minimum (<m> days) — `defaultExpiresAfterDays` is set and below `minimumExpiryDays`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The default expiry (<n> days) is shorter than the minimum (<m> days)","path":"/storefront/giftcards/policy","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"description":"Omitted fields keep their current value. `defaultExpiresAfterDays` null or 0 means purchased cards never expire by default. A default shorter than the minimum is refused.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/policy (body) -> The policy now in force\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DEFAULT_BELOW_MINIMUM | The default expiry (<n> days) is shorter than the minimum (<m> days) | `defaultExpiresAfterDays` is set and below `minimumExpiryDays`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"defaultExpiresAfterDays":{"type":"integer","nullable":true,"description":"Days until a purchased card expires when no date is given; null = never."},"minimumExpiryDays":{"type":"integer","description":"Earliest allowed expiry for a purchased card, in days from issue.","example":1825}}},"example":{"defaultExpiresAfterDays":1825,"minimumExpiryDays":1825}}}}}},"/storefront/giftcards/batches/{batch}/details":{"post":{"operationId":"GiftCardController_updateBatch","summary":"Rename a batch or add a note","description":"Writes `name` (as `batchName`) and/or `note` onto every card in the batch. An empty string clears it.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/batches/{batch}/details (batch: string, body) -> How many cards were updated\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOTHING_TO_CHANGE | Nothing to change | Neither `name` nor `note` was sent. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"batch","required":true,"in":"path","schema":{"type":"string"},"description":"Batch id (`BATCH-…`).","example":"BATCH-7K2M-9QX4"}],"responses":{"201":{"description":"How many cards were updated","content":{"application/json":{"schema":{"type":"object","properties":{"batch":{"type":"string"},"updated":{"type":"integer"},"name":{"type":"string"},"note":{"type":"string"}}}}}},"400":{"description":"Nothing to change — Neither `name` nor `note` was sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Nothing to change","path":"/storefront/giftcards/batches/{batch}/details","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"note":{"type":"string"}}},"example":{"name":"Holiday stock 2026","note":"Printed run, box 3"}}}}}},"/storefront/giftcards/batches/{batch}/cards":{"get":{"operationId":"GiftCardController_batchCards","summary":"Cards in a batch","description":"Every card in the batch, oldest first, for re-export. Includes the redemption codes — **never the PINs**.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/batches/{batch}/cards (batch: string) -> The batch's cards\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"batch","required":true,"in":"path","schema":{"type":"string"},"description":"Batch id (`BATCH-…`).","example":"BATCH-7K2M-9QX4"}],"responses":{"200":{"description":"The batch's cards","content":{"application/json":{"schema":{"type":"object","properties":{"batch":{"type":"string"},"data":{"type":"array","items":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}},"total":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/batches/{batch}/{action}":{"post":{"operationId":"GiftCardController_batchAction","summary":"Block, unblock or cancel a whole batch","description":"`action` is `suspend`, `reactivate` or `cancel` — lost stock, leaked codes. Each card goes through the same guarded action as a single card, so a card with a redemption on it is never cancelled. Cards already in the target state (or, for reactivate, not blocked) are skipped with the reason. A `reason` is required for suspend and cancel.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/batches/{batch}/{action} (batch: string, action: string, body) -> What was done and what was skipped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_ACTION | Unknown batch action | `action` is not suspend, reactivate or cancel. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"batch","required":true,"in":"path","schema":{"type":"string"},"description":"Batch id (`BATCH-…`).","example":"BATCH-7K2M-9QX4"},{"name":"action","in":"path","required":true,"description":"suspend, reactivate or cancel.","schema":{"type":"string","enum":["suspend","reactivate","cancel"]},"example":"suspend"}],"responses":{"201":{"description":"What was done and what was skipped","content":{"application/json":{"schema":{"type":"object","properties":{"batch":{"type":"string"},"action":{"type":"string"},"done":{"type":"integer"},"skipped":{"type":"array","items":{"type":"object","properties":{"serial":{"type":"string"},"reason":{"type":"string"}}}}}}}}},"400":{"description":"Unknown batch action — `action` is not suspend, reactivate or cancel.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown batch action","path":"/storefront/giftcards/batches/{batch}/{action}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Box of cards reported stolen"}}}}}},"/storefront/giftcards/activity":{"get":{"operationId":"GiftCardController_activity","summary":"Gift card activity","description":"Every movement on every card — redemptions, refunds, reloads, adjustments, transfers — newest first, filtered and paged in the database, with totals for the filtered set by kind and (for redemptions) by channel. `search` matches serial, holder name/email/phone, order number, redeemer and GCT reference.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/activity (kind?: string, channel?: string, from?: string, to?: string, batch?: string, search?: string, page?: integer, pageSize?: integer) -> A page of movements with summary totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/giftcards/{serial}/timeline`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"kind","required":false,"in":"query","schema":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]}},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"description":"checkout, pos, invoice, admin, gateway."},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"Date, YYYY-MM-DD.","example":"2026-09-01"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"Date, YYYY-MM-DD.","example":"2026-09-30"},{"name":"batch","required":false,"in":"query","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":50},"description":"Max 200."}],"responses":{"200":{"description":"A page of movements with summary totals","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"serial":{"type":"string"},"cardName":{"type":"string"},"type":{"type":"string"},"batch":{"type":"string"},"currency":{"type":"string"},"holderName":{"type":"string"},"holderEmail":{"type":"string"},"holderPhone":{"type":"string"},"customerId":{"type":"string"},"transactionId":{"type":"string"},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on."},"balanceAfter":{"type":"number"},"orderNumber":{"type":"string"},"usedBy":{"type":"string"},"channel":{"type":"string"},"reason":{"type":"string"}}}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"},"summary":{"type":"object","properties":{"byKind":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"count":{"type":"integer"},"amount":{"type":"number","description":"Absolute value."}}}},"byChannel":{"type":"array","items":{"type":"object","properties":{"channel":{"type":"string"},"count":{"type":"integer"},"amount":{"type":"number"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/link-owners":{"post":{"operationId":"GiftCardController_linkOwners","summary":"Link cards to customers","description":"Backfill: every card with no `customerId` whose recipient email (or buyer email) or phone matches an existing customer is linked to that customer. Never creates customers. Looks at up to 5000 unowned cards per call.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/link-owners () -> How many were checked and linked\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"How many were checked and linked","content":{"application/json":{"schema":{"type":"object","properties":{"checked":{"type":"integer"},"linked":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/{serial}":{"get":{"operationId":"GiftCardController_getBySerial","summary":"Get a gift card","description":"One card by serial, with its balance and full ledger. Exposes the redemption `code`, so it is staff only — shoppers use `GET /client/giftcards/balance`, or `GET /client/giftcards/me` for their own cards.\n\n#### Signature\n\n```http\nGET /storefront/giftcards/{serial} (serial: string) -> The gift card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"200":{"description":"The gift card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/{serial}/timeline":{"get":{"operationId":"GiftCardController_timeline","summary":"A card's timeline","description":"One card's whole story, newest first: money movements (`type: money`), sends (`type: sent`) and everything else — status, holder and template changes (`type: event`).\n\n#### Signature\n\n```http\nGET /storefront/giftcards/{serial}/timeline (serial: string) -> The timeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"200":{"description":"The timeline","content":{"application/json":{"schema":{"type":"object","properties":{"serial":{"type":"string"},"currency":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","format":"date-time"},"type":{"type":"string","enum":["money","sent","event"]},"kind":{"type":"string"},"amount":{"type":"number"},"balanceAfter":{"type":"number"},"orderNumber":{"type":"string"},"by":{"type":"string"},"channel":{"type":"string"},"to":{"type":"string"},"reason":{"type":"string"},"detail":{"type":"string"},"reference":{"type":"string"}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"]}},"/storefront/giftcards/{serial}/reissue":{"post":{"operationId":"GiftCardController_reissue","summary":"Reissue a card","description":"Replaces a damaged, lost or leaked card: a new card is issued to the same holder with the same rules and expiry, the whole balance moves onto it, and the old card is voided — both ledgers record the move. The new card is then sent to the holder; `delivery` says how that went.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/{serial}/reissue (serial: string, body) -> The voided card, the new one and the send result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"201":{"description":"The voided card, the new one and the send result","content":{"application/json":{"schema":{"type":"object","properties":{"old":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}},"card":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}},"delivery":{"type":"object","properties":{"delivered":{"type":"boolean"},"to":{"type":"string"},"channels":{"type":"array","items":{"type":"object","properties":{"channel":{"type":"string","enum":["email","sms"]},"to":{"type":"string"}}}},"reason":{"type":"string","description":"Why nothing was sent, when `delivered` is false."}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/reissue","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}},"example":{"reason":"Code shared publicly"}}}}}},"/storefront/giftcards/{serial}/activate":{"put":{"operationId":"GiftCardController_activate","summary":"Activate a gift card","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"200":{"description":"The activated card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/activate","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"description":"Brings an inactive card into service. This is the step that arms physical stock once it is sold. A card already in another state is refused rather than treated as a no-op.\n\n#### Signature\n\n```http\nPUT /storefront/giftcards/{serial}/activate (serial: string) -> The activated card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/giftcards/{serial}/suspend`"}},"/storefront/giftcards/{serial}/suspend":{"put":{"operationId":"GiftCardController_suspend","summary":"Suspend a gift card","description":"Reversible block — the response to a card reported lost or suspected stolen. The balance is untouched and redemption fails while it is suspended. Use this rather than cancel for anything you might undo, and for any card that has been spent against, since those cannot be cancelled.\n\n#### Signature\n\n```http\nPUT /storefront/giftcards/{serial}/suspend (serial: string, body) -> The suspended card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/giftcards/{serial}/reactivate`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"200":{"description":"The suspended card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/suspend","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"Why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Reported stolen"}}}}}}}},"/storefront/giftcards/{serial}/reactivate":{"put":{"operationId":"GiftCardController_reactivate","summary":"Reactivate a suspended gift card","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"200":{"description":"The reactivated card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/reactivate","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"description":"Lifts a suspension. The card returns to the state it had before — active, or inactive if it had never been activated — with its balance intact.\n\n#### Signature\n\n```http\nPUT /storefront/giftcards/{serial}/reactivate (serial: string) -> The reactivated card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/storefront/giftcards/{serial}/cancel":{"put":{"operationId":"GiftCardController_cancel","summary":"Cancel a gift card","description":"Voids an **unused** card permanently. **A card with a redemption on it cannot be cancelled** — its ledger is a record of value already given to a customer. Suspend it instead.\n\n#### Signature\n\n```http\nPUT /storefront/giftcards/{serial}/cancel (serial: string, body) -> The cancelled card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"200":{"description":"The cancelled card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"Why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Issued in error"}}}}}}}},"/storefront/giftcards/{serial}/refund":{"post":{"operationId":"GiftCardController_refund","summary":"Refund value onto a gift card","description":"Adds balance back, recording it against an order — a returned item refunded to store credit, or a redemption reversed. A fully-used card becomes active again. Send an `Idempotency-Key` header (or `idempotencyKey` in the body). A repeat with the same key returns the first result and moves no value — the site's checkout always sends one.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/{serial}/refund (serial: string, body) -> The card with its increased balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The amount is not checked against what was originally redeemed; a card can hold more than its initial value.\n- Returns completed with `refundMethod: giftcard` call this themselves — see `POST /storefront/returns/{rma}/complete`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional.","schema":{"type":"string"}}],"responses":{"201":{"description":"The card with its increased balance","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"How much, against what.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","orderNumber"],"properties":{"amount":{"type":"number","example":14.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"reason":{"type":"string"},"idempotencyKey":{"type":"string"}}}}}}}},"/storefront/giftcards/{serial}/resend":{"post":{"operationId":"GiftCardController_resend","summary":"Resend a gift card email","description":"Emails the card again — to its recipient, or to the address given. The operator's answer to \"it never arrived\". The email carries the code, never a PIN.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/{serial}/resend (serial: string, body) -> Whether it went, and where\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"201":{"description":"Whether it went, and where","content":{"application/json":{"schema":{"type":"object","properties":{"delivered":{"type":"boolean"},"to":{"type":"string"},"reason":{"type":"string"}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/resend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"Optional other address.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","format":"email"}}}}}}}},"/storefront/giftcards/{serial}/send":{"post":{"operationId":"GiftCardController_send","summary":"Send a gift card","description":"Sends the card by email and by SMS through the platform notification channels, using the card's message template (or `gift-card-issued`) and its SMS sibling. An `email`/`phone` given here replaces the one on the card (`to` may be either); with neither on the card, the buyer's email is used. Every send is recorded on the card.\n\n**Not being able to send is a `201` with `delivered: false`** and a `reason` (e.g. \"no email or phone on the card\") — check it.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/{serial}/send (serial: string, body) -> What was sent where\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/giftcards/{serial}/resend`\n- `POST /storefront/giftcards/{serial}/template`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"201":{"description":"What was sent where","content":{"application/json":{"schema":{"type":"object","properties":{"delivered":{"type":"boolean"},"to":{"type":"string"},"channels":{"type":"array","items":{"type":"object","properties":{"channel":{"type":"string","enum":["email","sms"]},"to":{"type":"string"}}}},"reason":{"type":"string","description":"Why nothing was sent, when `delivered` is false."}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"phone":{"type":"string"},"to":{"type":"string","description":"An email or a phone; used when neither `email` nor `phone` is given."}}},"example":{"email":"ada@example.com"}}}}}},"/storefront/giftcards/{serial}/owner":{"post":{"operationId":"GiftCardController_setOwner","summary":"Set the holder of a card","description":"Pick a customer by `customerId` — their name, email and phone come with them (anything also given in the body wins) — or give `name`/`email`/`phone`, linked to a matching customer when one exists. Recorded as a holder-changed event.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/{serial}/owner (serial: string, body) -> The card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"201":{"description":"The card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/owner","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}},"example":{"customerId":"66f1c0ffee12"}}}}}},"/storefront/giftcards/{serial}/template":{"post":{"operationId":"GiftCardController_setTemplate","summary":"Set the message template a card is sent with","description":"The email template used by `send` (its SMS sibling is used for texts). An empty or missing `messageTemplate` clears it back to the platform default.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/{serial}/template (serial: string, body) -> The card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"201":{"description":"The card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/template","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"messageTemplate":{"type":"string","example":"holiday-gift-card"}}}}}}}},"/storefront/giftcards/{serial}/adjust":{"post":{"operationId":"GiftCardController_adjust","summary":"Adjust a gift card balance","description":"Operator correction, up or down, always with a reason, recorded on the card's ledger as `kind: adjust`. Cannot take the balance below zero.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/{serial}/adjust (serial: string, body) -> The adjusted card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GIFT_CARD_NOT_FOUND | Gift card <serial> not found | No gift card in the org has that serial. | Check the serial with `GET /storefront/giftcards?search=`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"},"description":"Gift card **serial number**, not the redemption code. Case-insensitive.","example":"GC-7K2M-9QX4-H1P7"}],"responses":{"201":{"description":"The adjusted card","content":{"application/json":{"schema":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}},"400":{"description":"Gift card <serial> not found — No gift card in the org has that serial.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gift card <serial> not found","path":"/storefront/giftcards/{serial}/adjust","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"Signed amount and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","reason"],"properties":{"amount":{"type":"number","description":"Positive adds, negative removes.","example":-5},"reason":{"type":"string","example":"Goodwill after a late delivery"}}}}}}}},"/storefront/giftcards/transfer":{"post":{"operationId":"GiftCardController_transfer","summary":"Transfer balance between gift cards","description":"Moves value from one active card to another of the same currency — consolidating balances, or reissuing a damaged card onto a fresh one. Unlike redemption the amount is **not** clamped: an over-ask is refused, not partially performed. Both cards are locked for the move.\n\n#### Signature\n\n```http\nPOST /storefront/giftcards/transfer (body) -> Both cards after the move\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Uses serials, not redemption codes — the opposite of `redeem` and `balance`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SOURCE_NOT_FOUND | Source gift card <serial> not found | `fromSerial` does not resolve. | Check the serial. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Both cards after the move","content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}},"to":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}}}}}},"400":{"description":"Source gift card <serial> not found — `fromSerial` does not resolve.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Source gift card <serial> not found","path":"/storefront/giftcards/transfer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards"],"requestBody":{"description":"From, to, how much.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["fromSerial","toSerial","amount"],"properties":{"fromSerial":{"type":"string"},"toSerial":{"type":"string"},"amount":{"type":"number"}}}}}}}},"/client/giftcards/balance":{"get":{"operationId":"GiftCardClientController_balance","summary":"Look a card up by its code (shopper)","description":"The shopper-side balance check: whether the card can be spent, what is left, and what is written on it — never an address or the PIN. Same answers as the staff check (`valid: false` with a `status` for an unusable card).\n\n**Throttled**: 30 checks per client address per 10 minutes, then `429`. A balance check is a code-existence oracle, and this is what keeps it from being an enumeration tool.\n\n#### Signature\n\n```http\nGET /client/giftcards/balance () -> The balance check\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `429` | TOO_MANY_REQUESTS | Too many balance checks. Try again in a few minutes. | More than 30 checks from one address in 10 minutes. | Wait, or use GET /client/giftcards/me, which lists the customer's own cards without a lookup. |\n\nPlus the standard platform errors: `401`, `403`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"}},{"name":"pin","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The balance check","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"description":"Too many balance checks. Try again in a few minutes. — More than 30 checks from one address in 10 minutes.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":429,"error":"Too many balance checks. Try again in a few minutes.","path":"/client/giftcards/balance","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards","Client account"]}},"/client/giftcards/redeem":{"post":{"operationId":"GiftCardClientController_redeem","summary":"Spend a card at checkout (shopper)","description":"Clamped to the balance and the per-transaction cap; `amountRedeemed` is the figure to trust. Send an idempotency key so a retry cannot spend twice. `usedBy` is the signed-in customer, else `guest`.\n\n#### Signature\n\n```http\nPOST /client/giftcards/redeem () -> The redemption, with its GCT reference\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"The redemption, with its GCT reference","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards","Client account"]}},"/client/giftcards/wallet/status":{"get":{"operationId":"GiftCardClientController_walletStatus","summary":"Which wallet passes this store can issue","description":"`{ apple, google }` — true when the store has put its pass credentials on the gift card integration (Payment gateways › Gift card). Show an Add-to-Wallet button only when true. `GET /client/giftcards/wallet/apple?code=` returns the signed `.pkpass`; `GET /client/giftcards/wallet/google?code=` returns `{ url }`, the Save to Google Wallet link. Both are throttled like the balance check; `501` until set up.\n\n#### Signature\n\n```http\nGET /client/giftcards/wallet/status () -> { apple, google }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ apple, google }","content":{"application/json":{"schema":{"type":"object","properties":{"apple":{"type":"boolean"},"google":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards","Client account"]}},"/client/giftcards/wallet/apple":{"get":{"operationId":"GiftCardClientController_applePass","summary":"Apple Wallet pass for my card","description":"The signed `.pkpass` for the card with this code, as a file download. Counts against the same per-address throttle as the balance check (30 checks per 10 minutes).\n\n#### Signature\n\n```http\nGET /client/giftcards/wallet/apple (code?: string) -> The .pkpass file (binary)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `501` | WALLET_NOT_SET_UP | Apple Wallet is not set up for this store | The pass credentials are not configured on the gift card integration. | Check GET …/wallet/status first and only offer the button when true. |\n| `400` | CODE_REQUIRED | A gift card code is needed | No `code`. | — |\n| `404` | GIFT_CARD_NOT_FOUND | Gift card not found | The code does not resolve. (This one is a real 404.) | — |\n| `429` | TOO_MANY_CHECKS | Too many gift card checks. Try again in a few minutes. | More than 30 checks from one address in 10 minutes (shared with the balance check). | — |\n\nPlus the standard platform errors: `401`, `403`, `500`.\n\n#### See also\n\n- `GET /client/giftcards/wallet/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"},"description":"The redemption code, not the serial.","example":"YMFB-CDZ2-KDXS-KLM9"}],"responses":{"200":{"description":"The .pkpass file (binary)","content":{"application/json":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"A gift card code is needed — No `code`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A gift card code is needed","path":"/client/giftcards/wallet/apple","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Gift card not found — The code does not resolve. (This one is a real 404.)","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Gift card not found","path":"/client/giftcards/wallet/apple","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"description":"Too many gift card checks. Try again in a few minutes. — More than 30 checks from one address in 10 minutes (shared with the balance check).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":429,"error":"Too many gift card checks. Try again in a few minutes.","path":"/client/giftcards/wallet/apple","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"500":{"$ref":"#/components/responses/ServerError"},"501":{"description":"Apple Wallet is not set up for this store — The pass credentials are not configured on the gift card integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":501,"error":"Apple Wallet is not set up for this store","path":"/client/giftcards/wallet/apple","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Gift cards","Client account"]}},"/client/giftcards/wallet/google":{"get":{"operationId":"GiftCardClientController_googlePass","summary":"Google Wallet link for my card","description":"The \"Save to Google Wallet\" link for the card with this code. Counts against the same per-address throttle as the balance check.\n\n#### Signature\n\n```http\nGET /client/giftcards/wallet/google (code?: string) -> { url }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `501` | WALLET_NOT_SET_UP | Google Wallet is not set up for this store | The pass credentials are not configured on the gift card integration. | Check GET …/wallet/status first and only offer the button when true. |\n| `400` | CODE_REQUIRED | A gift card code is needed | No `code`. | — |\n| `404` | GIFT_CARD_NOT_FOUND | Gift card not found | The code does not resolve. (This one is a real 404.) | — |\n| `429` | TOO_MANY_CHECKS | Too many gift card checks. Try again in a few minutes. | More than 30 checks from one address in 10 minutes (shared with the balance check). | — |\n\nPlus the standard platform errors: `401`, `403`, `500`.\n\n#### See also\n\n- `GET /client/giftcards/wallet/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"},"description":"The redemption code, not the serial.","example":"YMFB-CDZ2-KDXS-KLM9"}],"responses":{"200":{"description":"{ url }","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}},"400":{"description":"A gift card code is needed — No `code`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A gift card code is needed","path":"/client/giftcards/wallet/google","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Gift card not found — The code does not resolve. (This one is a real 404.)","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Gift card not found","path":"/client/giftcards/wallet/google","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"description":"Too many gift card checks. Try again in a few minutes. — More than 30 checks from one address in 10 minutes (shared with the balance check).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":429,"error":"Too many gift card checks. Try again in a few minutes.","path":"/client/giftcards/wallet/google","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"500":{"$ref":"#/components/responses/ServerError"},"501":{"description":"Google Wallet is not set up for this store — The pass credentials are not configured on the gift card integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":501,"error":"Google Wallet is not set up for this store","path":"/client/giftcards/wallet/google","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Gift cards","Client account"]}},"/client/giftcards/me":{"get":{"operationId":"GiftCardClientController_myCards","summary":"My gift cards","description":"The signed-in customer's cards — held, bought, or sent to them — held ones first. A card they gave away shows only its last four. Includes `usableBalance` and the store's top-up product. Never the PIN. The account page and the checkout's \"your gift cards\" picker read this.\n\n#### Signature\n\n```http\nGET /client/giftcards/me () -> The customer's cards\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The customer's cards","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A gift card (`sf_gift_card`). The PIN hash is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"serial":{"type":"string","description":"Operator identifier. Every admin endpoint addresses a card by this.","example":"GC-7K2M-9QX4-H1P7"},"code":{"type":"string","description":"Redemption code given to the customer. Distinct from `serial`. Letters exclude 0/O/1/I; separators are ignored on lookup.","example":"YMFB-CDZ2-KDXS-KLM9"},"name":{"type":"string","example":"Holiday gift card"},"type":{"type":"string","enum":["physical","digital","promotional"],"example":"digital","description":"`promotional` cards were never paid for and are not a liability."},"status":{"type":"string","enum":["inactive","active","used","expired","cancelled","suspended"],"example":"active"},"currency":{"type":"string","example":"USD"},"initialAmount":{"type":"number","description":"Value the card was issued with.","example":50},"balance":{"type":"number","description":"Remaining value. Only the ledger moves this.","example":35.5},"batch":{"type":"string","description":"Set when issued as part of a batch.","example":"BATCH-7K2M-9QX4"},"expirationDate":{"type":"string","format":"date-time","description":"Absent means the card never expires — the default for purchased cards."},"purchasedBy":{"type":"string","description":"Buyer email."},"purchaseOrderNumber":{"type":"string","description":"The order that sold it."},"customerId":{"type":"string","description":"The customer whose wallet holds it."},"recipientName":{"type":"string"},"recipientEmail":{"type":"string"},"recipientPhone":{"type":"string","description":"Where the card is texted."},"recipientMessage":{"type":"string"},"messageTemplate":{"type":"string","description":"Email template the card is sent with; its SMS sibling is used for texts. Empty means the platform default (`gift-card-issued`)."},"campaign":{"type":"string","description":"Promotional campaign the card belongs to — reported in GET /storefront/giftcards/insights."},"batchName":{"type":"string","description":"Display name of the batch (POST /storefront/giftcards/batches/{batch}/details)."},"deliverAt":{"type":"string","format":"date-time","description":"When the card email is scheduled to go; absent means no send is scheduled (see POST /storefront/giftcards/{serial}/send)."},"deliveredAt":{"type":"string","format":"date-time"},"restrictions":{"type":"object","description":"Limits on where the card may be spent. Enforced at redemption when the order context is supplied.","properties":{"minPurchase":{"type":"number"},"maxUsePerTransaction":{"type":"number"},"productSkus":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}},"customerGroups":{"type":"array","items":{"type":"string"}},"excludeDiscountedItems":{"type":"boolean"}}},"uses":{"type":"array","description":"Every movement of value, oldest first.","items":{"type":"object","properties":{"transactionId":{"type":"string","description":"GCT-… reference. A `giftcard` payment is verified by this.","example":"GCT-VB8C-SZ7F-E33V"},"idempotencyKey":{"type":"string","description":"Caller-supplied; a repeat with the same key returns the first result."},"kind":{"type":"string","enum":["redeem","refund","reload","adjust","transfer_in","transfer_out"]},"date":{"type":"string","format":"date-time"},"amount":{"type":"number","description":"Positive takes value off the card, negative puts it on.","example":14.5},"balanceAfter":{"type":"number","example":35.5},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"usedBy":{"type":"string","description":"Email of the redeemer, or `guest`."},"reason":{"type":"string"},"channel":{"type":"string","description":"checkout, pos, invoice, admin, gateway."}}}}}}}}},"total":{"type":"integer"},"usableBalance":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards","Client account"]}},"/client/giftcards/me/{serial}/share":{"post":{"operationId":"GiftCardClientController_share","summary":"Send part of my card to someone","description":"Moves `amount` from the holder's card onto a new card for someone else (`name`, `email` and/or `phone`, `message`) and sends it to them. Only the card's holder may.\n\n#### Signature\n\n```http\nPOST /client/giftcards/me/{serial}/share () -> The new card and what is left on the holder's\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"serial","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"The new card and what is left on the holder's","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Gift cards","Client account"]}},"/storefront/returns/request":{"post":{"operationId":"ReturnsController_createReturnRequest","summary":"Create a return request","description":"Opens an RMA against an order. The return starts in `requested` and waits for an operator to approve or reject it.\n\nThe order must exist and still be inside its return window — a request against an out-of-window order is refused here rather than at approval, so the customer finds out immediately.\n\nThe customer's identity comes from the signed-in caller. **An anonymous caller is recorded as `guest@example.com`**, which then becomes the only handle on the return, so require sign-in before exposing this.\n\n```\nrequested ──approve──> approved ──ship──> shipped ──receive──> received ──inspect──> inspecting\n    │                                                                  │                     │\n    └──reject──> rejected                                              └────complete─────────┴──> completed\n```\n\nEvery transition checks the exact status before it, so steps cannot be skipped — the one exception is `complete`, which accepts either `received` or `inspecting`. A refund can never exceed the value of the lines accepted at inspection, so completing straight from `received` only allows a refund of 0. `cancel` works from any status except `completed` and `cancelled`.\n\n#### Signature\n\n```http\nPOST /storefront/returns/request (body) -> The created return, with its RMA number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No check stops a customer requesting a return for more items than they ordered, or requesting twice for the same line — approval is the control point.\n- Anonymous callers are stored as `guest@example.com`, which makes their returns indistinguishable from each other.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORDER_NOT_FOUND | Order <orderNumber> not found | The order number does not resolve in the org. | Check the order number. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/returns/my-returns`\n- `PUT /storefront/returns/{rmaNumber}/approve`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The created return, with its RMA number","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Order <orderNumber> not found — The order number does not resolve in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Order <orderNumber> not found","path":"/storefront/returns/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"The order, the reason, and the items coming back.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderNumber","reason","reasonCategory","items"],"properties":{"orderNumber":{"type":"string","description":"Public order number the return is against.","example":"A7K2M9QX4"},"reason":{"type":"string","description":"Free-text reason for the whole request.","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"description":"Structured reason, used for reporting.","example":"damaged_shipping"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"default":"return","description":"What the customer wants — a refund, a swap, a warranty claim or a repair.","example":"return"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}},"description":"The lines being returned, with quantities and condition."},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"description":"The customer's preference. The operator sets the actual method at completion.","example":"original_payment"}}},"examples":{"damaged":{"summary":"Two damaged cans from a four-pack order","value":{"orderNumber":"A7K2M9QX4","reason":"Damaged on arrival","reasonCategory":"damaged_shipping","type":"return","items":[{"sku":"DRK-COLA-330","name":"Cola 330ml","quantity":4,"returnQuantity":2,"price":12,"reason":"Two cans arrived dented","condition":"damaged"}],"refundMethod":"original_payment"}},"exchange":{"summary":"Exchange for the right item","value":{"orderNumber":"A7K2M9QX4","reason":"Received the wrong size","reasonCategory":"wrong_item","type":"exchange","items":[{"sku":"DRK-COLA-500","name":"Cola 500ml","quantity":1,"returnQuantity":1,"price":15,"reason":"Ordered 330ml","condition":"unopened"}],"refundMethod":"exchange"}}}}}}}},"/storefront/returns/my-returns":{"get":{"operationId":"ReturnsController_getMyReturns","summary":"Get the calling customer's returns","description":"Every return request belonging to the signed-in customer, matched on their email. The self-service list for an account page.\n\n#### Signature\n\n```http\nGET /storefront/returns/my-returns () -> The customer's returns\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Matched on the caller's email, so an anonymous caller sees every return recorded against `guest@example.com`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/returns/rma/{rmaNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The customer's returns","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"]}},"/storefront/returns/rma/{rmaNumber}":{"get":{"operationId":"ReturnsController_getByRmaNumber","summary":"Get a return by RMA number","description":"Fetches one return with its items and full timeline — the customer-facing status page for \"where is my return\".\n\nThe RMA number is the only credential: this route does not check the return belongs to the caller. Treat RMA numbers as unguessable.\n\n#### Signature\n\n```http\nGET /storefront/returns/rma/{rmaNumber} (rmaNumber: string) -> The return request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/rma/{rmaNumber}/ship`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The return request","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/rma/{rmaNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"]}},"/storefront/returns/rma/{rmaNumber}/ship":{"put":{"operationId":"ReturnsController_markShipped","summary":"Mark a return as shipped","description":"The customer records that they have sent the package back, supplying the carrier and tracking number. The return moves to `shipped`.\n\nIt must be `approved` first — a customer cannot ship a return that has not been authorised, which is the point of the RMA.\n\nWhen the approval did not include a return label, the carrier and tracking sent here become the return's shipping label.\n\n#### Signature\n\n```http\nPUT /storefront/returns/rma/{rmaNumber}/ship (rmaNumber: string, body) -> The shipped return\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/receive`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The shipped return","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/rma/{rmaNumber}/ship","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"How the package was sent back.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["carrier","trackingNumber"],"properties":{"carrier":{"type":"string","example":"ups"},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"}}},"example":{"carrier":"ups","trackingNumber":"1Z999AA10123456784"}}}}}},"/storefront/returns/rma/{rmaNumber}/cancel":{"put":{"operationId":"ReturnsController_cancelByCustomer","summary":"Cancel a return (customer)","description":"The customer withdraws their return request. Available at any point until the return is completed or already cancelled — including after shipping, though at that point the package is already in transit.\n\nThe operator equivalent is `PUT /storefront/returns/{rmaNumber}/cancel`; both behave identically, and this one exists so the customer surface stays under `/rma/`.\n\n#### Signature\n\n```http\nPUT /storefront/returns/rma/{rmaNumber}/cancel (rmaNumber: string, body) -> The cancelled return\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This route does not verify the return belongs to the caller — the RMA number alone is enough to cancel it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The cancelled return","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/rma/{rmaNumber}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"Why the customer is cancelling.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Recorded in the timeline.","example":"Decided to keep the item"}}},"example":{"reason":"Decided to keep the item"}}}}}},"/storefront/returns":{"get":{"operationId":"ReturnsController_list","summary":"List returns","description":"Lists return requests across the org with optional filters and paging. The operator queue.\n\n#### Signature\n\n```http\nGET /storefront/returns (status?: string, type?: string, page?: integer, pageSize?: integer) -> A page of returns\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Filter on `status=requested` for the queue of returns awaiting a decision.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/returns/stats`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by lifecycle status.","example":"requested"},{"name":"type","required":false,"in":"query","schema":{"type":"string","enum":["return","exchange","warranty","repair"]},"example":"return"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of returns","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}},"total":{"type":"integer","example":24}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"]}},"/storefront/returns/stats":{"get":{"operationId":"ReturnsController_getStats","summary":"Get return statistics","description":"Aggregate return figures for the org — volume, reasons and refunded value. The read behind a returns dashboard.\n\n#### Signature\n\n```http\nGET /storefront/returns/stats () -> Aggregate return statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Declared before `GET /storefront/returns/{rmaNumber}`-style routes, so `stats` always resolves as this endpoint.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/returns`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Aggregate return statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"]}},"/storefront/returns/order/{orderNumber}":{"get":{"operationId":"ReturnsController_getByOrder","summary":"Get returns for an order","description":"Every return raised against one order. An order can have several — a customer may return different lines at different times.\n\n#### Signature\n\n```http\nGET /storefront/returns/order/{orderNumber} (orderNumber: string) -> The returns raised against that order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns an empty array for an order with no returns, rather than an error.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/returns`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","description":"Public order number.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"The returns raised against that order","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"]}},"/storefront/returns/{rmaNumber}/approve":{"put":{"operationId":"ReturnsController_approve","summary":"Approve a return request","description":"Authorises the return and tells the customer where to send the goods. The return moves to `approved` and `approvedDate` is stamped.\n\n`returnAddress` is required — an approved return with nowhere to ship is not actionable. Include a `shippingLabel` to send the customer a prepaid label; `paidBy` records whether the store or the customer bears the cost. Without one, the customer supplies their own carrier and tracking when they ship.\n\nOnly a return still in `requested` can be approved.\n\n#### Signature\n\n```http\nPUT /storefront/returns/{rmaNumber}/approve (rmaNumber: string, body) -> The approved return\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/reject`\n- `PUT /storefront/returns/rma/{rmaNumber}/ship`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The approved return","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/{rmaNumber}/approve","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"Where to ship the goods, and optionally a prepaid label.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["returnAddress"],"properties":{"returnAddress":{"type":"object","required":["name","street","city","state","postalCode","country"],"properties":{"name":{"type":"string","example":"Acme Returns"},"street":{"type":"string","example":"4 Warehouse Way"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"postalCode":{"type":"string","example":"07102"},"country":{"type":"string","example":"US"}}},"shippingLabel":{"type":"object","description":"A prepaid return label. Omit to have the customer arrange and pay for shipping.","required":["carrier","trackingNumber","paidBy"],"properties":{"carrier":{"type":"string","example":"ups"},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"labelUrl":{"type":"string","description":"Where the customer downloads the label.","example":"https://cdn.appmint.io/labels/rma-4821.pdf"},"cost":{"type":"number","example":8.5},"paidBy":{"type":"string","enum":["customer","store"],"example":"store"}}},"notes":{"type":"string","description":"Recorded in the timeline.","example":"Approved — damaged in transit, our cost"}}},"examples":{"prepaid":{"summary":"Approve with a prepaid label","value":{"returnAddress":{"name":"Acme Returns","street":"4 Warehouse Way","city":"Newark","state":"NJ","postalCode":"07102","country":"US"},"shippingLabel":{"carrier":"ups","trackingNumber":"1Z999AA10123456784","labelUrl":"https://cdn.appmint.io/labels/rma-4821.pdf","cost":8.5,"paidBy":"store"}}},"customerPays":{"summary":"Approve, customer arranges shipping","value":{"returnAddress":{"name":"Acme Returns","street":"4 Warehouse Way","city":"Newark","state":"NJ","postalCode":"07102","country":"US"},"notes":"Customer to return at own cost per policy"}}}}}}}},"/storefront/returns/{rmaNumber}/reject":{"put":{"operationId":"ReturnsController_reject","summary":"Reject a return request","description":"Declines the return. The reason is recorded in the timeline and is what the customer is told.\n\n**Rejection is terminal.** There is no transition back to `requested`, so a rejected return cannot later be approved — the customer must raise a new request.\n\n#### Signature\n\n```http\nPUT /storefront/returns/{rmaNumber}/reject (rmaNumber: string, body) -> The rejected return\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Irreversible — a rejected return has no path back into the lifecycle.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/approve`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The rejected return","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/{rmaNumber}/reject","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"Why the return is being declined.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Shown to the customer and recorded in the timeline.","example":"Outside the 30-day return window"}}},"example":{"reason":"Outside the 30-day return window"}}}}}},"/storefront/returns/{rmaNumber}/receive":{"put":{"operationId":"ReturnsController_markReceived","summary":"Mark a return as received","description":"Records that the package has arrived at the warehouse. The return moves to `received` and `receivedDate` is stamped.\n\nThe return must be `shipped` first, so a package cannot be received before the customer says it was sent. If a customer returns goods without marking them shipped, mark it shipped on their behalf first.\n\n#### Signature\n\n```http\nPUT /storefront/returns/{rmaNumber}/receive (rmaNumber: string, body) -> The received return\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/inspect`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The received return","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/{rmaNumber}/receive","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"Optional receiving notes.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","default":"Package received","description":"Recorded in the timeline.","example":"Box arrived crushed, contents intact"}}},"example":{"notes":"Box arrived crushed, contents intact"}}}}}},"/storefront/returns/{rmaNumber}/inspect":{"put":{"operationId":"ReturnsController_inspect","summary":"Record inspection results","description":"Records what the warehouse actually found: one decision for **every** returned line — accepted for refund or not — and the condition it arrived in. The return moves to `inspecting` and `refundAmount` is set to the value of the accepted lines (price × return quantity), which is the most `complete` will refund.\n\nLines are matched by `sku`; when the same SKU appears on more than one line, identify each with `itemIndex`.\n\n#### Signature\n\n```http\nPUT /storefront/returns/{rmaNumber}/inspect (rmaNumber: string, body) -> The return with inspection results and refundAmount\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/complete`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The return with inspection results and refundAmount","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/{rmaNumber}/inspect","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"Per-line findings.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","description":"Exactly one entry per returned line.","items":{"type":"object","required":["sku","approved","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"itemIndex":{"type":"integer","description":"Position of the line on the return; required when a SKU repeats."},"approved":{"type":"boolean","description":"Accepted for refund.","example":true},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as found.","example":"damaged"},"notes":{"type":"string","example":"Both cans dented, matches the claim"}}}},"overallNotes":{"type":"string","example":"Consistent with shipping damage"}}},"example":{"items":[{"sku":"DRK-COLA-330","approved":true,"condition":"damaged","notes":"Both cans dented, matches the claim"}],"overallNotes":"Consistent with shipping damage"}}}}}},"/storefront/returns/{rmaNumber}/complete":{"put":{"operationId":"ReturnsController_complete","summary":"Complete a return and refund","description":"Closes the return (`completed`, `completedDate`) and records the refund. **The refund can never exceed the value of the lines accepted at inspection** — completing straight from `received` (no inspection) therefore only allows `amount: 0`.\n\n- `store_credit` / `giftcard`: this call moves the money — onto `giftcardSerial` when given, otherwise a new digital card is issued to the customer and emailed. `refundStatus` is `completed`.\n- `original_payment` / `exchange`: the money is moved elsewhere; pass the reference in `transactionId`. Recorded POS cash refund ids (comma-separated) are verified — they must be complete and total the amount — and mark the refund `completed`; any other reference is recorded as-is (`refundStatus: recorded`), unverified.\n\nWhen completed returns now cover every unit sold, the order becomes `returned`.\n\n#### Signature\n\n```http\nPUT /storefront/returns/{rmaNumber}/complete (rmaNumber: string, body) -> The completed return\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Completion is terminal: a completed return cannot be cancelled.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/inspect`\n- `POST /storefront/giftcards/{serial}/refund`\n- `POST /storefront/pos/tab/{id}/refund`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The completed return","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/{rmaNumber}/complete","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"How much to refund, and by what route.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","method"],"properties":{"amount":{"type":"number","description":"Non-negative, at most the accepted value.","example":24},"method":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"store_credit"},"transactionId":{"type":"string","description":"External refund reference, or recorded POS cash refund ids (comma-separated). Required for a non-zero refund not credited to a card.","example":"re_3PabcXYZ"},"giftcardSerial":{"type":"string","description":"Card to credit for giftcard / store_credit. Omit to issue a new card.","example":"GC-4821-9917"}}},"examples":{"credit":{"summary":"Store credit on a new card","value":{"amount":24,"method":"store_credit"}},"giftcard":{"summary":"Back onto an existing gift card","value":{"amount":24,"method":"giftcard","giftcardSerial":"GC-4821-9917"}},"original":{"summary":"Refunded at the gateway, reference recorded","value":{"amount":24,"method":"original_payment","transactionId":"re_3PabcXYZ"}}}}}}}},"/storefront/returns/{rmaNumber}/notes":{"post":{"operationId":"ReturnsController_addNote","summary":"Add a note to a return","description":"Appends an internal note to the return timeline. Available in any status, including after completion, so the record can always be annotated.\n\n#### Signature\n\n```http\nPOST /storefront/returns/{rmaNumber}/notes (rmaNumber: string, body) -> The return with the note appended\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Notes are internal. They sit in the same timeline the customer-facing status page reads, so do not put anything in them you would not show the customer.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/returns/rma/{rmaNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"201":{"description":"The return with the note appended","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/{rmaNumber}/notes","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"The note to add.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string","description":"Appended to the timeline, attributed to the caller.","example":"Customer called to chase — advised 3 working days"}}},"example":{"note":"Customer called to chase — advised 3 working days"}}}}}},"/storefront/returns/{rmaNumber}/cancel":{"put":{"operationId":"ReturnsController_cancel","summary":"Cancel a return (operator)","description":"Cancels a return from the operator side. Works from any status except `completed` and `cancelled`.\n\nIdentical in behaviour to the customer route `PUT /storefront/returns/rma/{rmaNumber}/cancel`; the two exist so the customer surface stays namespaced under `/rma/`.\n\n#### Signature\n\n```http\nPUT /storefront/returns/{rmaNumber}/cancel (rmaNumber: string, body) -> The cancelled return\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RETURN_NOT_FOUND | Return <rmaNumber> not found | No return in the org has that RMA number. | Check the RMA number. Note this is a `400`, not a `404` — the whole service raises 400 for missing records. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/rma/{rmaNumber}/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rmaNumber","required":true,"in":"path","description":"RMA number.","schema":{"type":"string"},"example":"RMA-4821"}],"responses":{"200":{"description":"The cancelled return","content":{"application/json":{"schema":{"type":"object","description":"A return request (`sf_return`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rmaNumber":{"type":"string","description":"Return authorisation number — the identifier for every endpoint here.","example":"RMA-4821"},"orderNumber":{"type":"string","example":"A7K2M9QX4"},"status":{"type":"string","enum":["requested","approved","rejected","shipped","received","inspecting","completed","cancelled"],"example":"approved"},"type":{"type":"string","enum":["return","exchange","warranty","repair"],"example":"return"},"reason":{"type":"string","example":"Damaged on arrival"},"reasonCategory":{"type":"string","enum":["defective","not_as_described","wrong_item","changed_mind","damaged_shipping","other"],"example":"damaged_shipping"},"customerEmail":{"type":"string","example":"ada@example.com"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","required":["sku","name","quantity","returnQuantity","price","reason","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"name":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"How many were originally ordered.","example":4},"returnQuantity":{"type":"number","description":"How many are being returned.","example":2},"price":{"type":"number","description":"Unit price paid.","example":12},"reason":{"type":"string","description":"Per-item reason, which can differ from the request-level one.","example":"Two cans arrived dented"},"condition":{"type":"string","enum":["unopened","opened","damaged","defective","wrong_item"],"description":"Condition as the customer describes it. The inspection step records the real one.","example":"damaged"}}}},"refundMethod":{"type":"string","enum":["original_payment","store_credit","exchange","giftcard"],"example":"original_payment"},"refundAmount":{"type":"number","description":"Set when the return completes.","example":24},"returnAddress":{"type":"object","additionalProperties":true,"description":"Where the customer ships to. Set at approval."},"shippingLabel":{"type":"object","additionalProperties":true,"description":"Return label, from approval or from the customer marking it shipped."},"timeline":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Append-only history of every transition and note."},"requestedDate":{"type":"string","format":"date-time"},"approvedDate":{"type":"string","format":"date-time"},"shippedDate":{"type":"string","format":"date-time"},"receivedDate":{"type":"string","format":"date-time"},"completedDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Return <rmaNumber> not found — No return in the org has that RMA number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Return <rmaNumber> not found","path":"/storefront/returns/{rmaNumber}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Returns"],"requestBody":{"description":"Why the return is being cancelled.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Recorded in the timeline.","example":"Duplicate request — superseded by RMA-4822"}}},"example":{"reason":"Duplicate request — superseded by RMA-4822"}}}}}},"/storefront/inventory/sku/{sku}":{"get":{"operationId":"InventoryController_getInventoryBySku","summary":"Get inventory for a SKU across locations","description":"Every stock level held for one SKU, one entry per location. This is the read for \"where can we ship this from\" — sum `availableQuantity` across the entries for total sellable stock.\n\n#### Signature\n\n```http\nGET /storefront/inventory/sku/{sku} (sku: string) -> Stock levels per location\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A location where the SKU has never been stocked has no record at all, so it is absent rather than reported as zero.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/inventory/sku/{sku}/location/{locationId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"sku","required":true,"in":"path","description":"Product SKU.","schema":{"type":"string"},"example":"DRK-COLA-330"}],"responses":{"200":{"description":"Stock levels per location","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory/sku/{sku}/location/{locationId}":{"get":{"operationId":"InventoryController_getInventory","summary":"Get inventory for a SKU at one location","description":"The stock level for one SKU at one location, with its on-hand, reserved and available quantities.\n\n#### Signature\n\n```http\nGET /storefront/inventory/sku/{sku}/location/{locationId} (sku: string, locationId: string) -> The stock level, or null when the SKU has never been stocked there\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns `null` with a `200` when there is no record — the write endpoints raise a `400` for the same condition.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"sku","required":true,"in":"path","description":"Product SKU.","schema":{"type":"string"},"example":"DRK-COLA-330"},{"name":"locationId","required":true,"in":"path","description":"Location id.","schema":{"type":"string"},"example":"loc_downtown"}],"responses":{"200":{"description":"The stock level, or null when the SKU has never been stocked there","content":{"application/json":{"schema":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory/location/{locationId}":{"get":{"operationId":"InventoryController_getInventoryByLocation","summary":"List inventory at a location","description":"Every stock level held at one location, paged. Set `lowStock=true` to narrow it to items at or below their reorder point — the picking list for a restock.\n\n#### Signature\n\n```http\nGET /storefront/inventory/location/{locationId} (locationId: string, lowStock?: boolean, page?: integer, pageSize?: integer) -> A page of stock levels\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/inventory/alerts/low-stock`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"locationId","required":true,"in":"path","description":"Location id.","schema":{"type":"string"},"example":"loc_downtown"},{"name":"lowStock","required":false,"in":"query","description":"Only items below their reorder point. Only the exact string `true` enables it.","schema":{"type":"boolean","default":false},"example":true},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of stock levels","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}},"total":{"type":"integer","example":340}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory/alerts/low-stock":{"get":{"operationId":"InventoryController_getLowStockAlerts","summary":"Get low stock alerts","description":"Every SKU at or below its reorder point, across the org or at one location. The reorder worklist.\n\n#### Signature\n\n```http\nGET /storefront/inventory/alerts/low-stock (locationId?: string) -> Stock levels below their reorder point\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A SKU with no `reorderPoint` set cannot trigger an alert.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/inventory/location/{locationId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"locationId","required":false,"in":"query","description":"Restrict to one location. Omit for the whole org.","schema":{"type":"string"},"example":"loc_downtown"}],"responses":{"200":{"description":"Stock levels below their reorder point","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory/stats":{"get":{"operationId":"InventoryController_getStats","summary":"Get inventory statistics","description":"Aggregate stock figures for the org or one location — totals, value and how many SKUs are below their reorder point.\n\n#### Signature\n\n```http\nGET /storefront/inventory/stats (locationId?: string) -> Aggregate inventory statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/inventory/alerts/low-stock`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"locationId","required":false,"in":"query","description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_downtown"}],"responses":{"200":{"description":"Aggregate inventory statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory/sku/{sku}/transactions":{"get":{"operationId":"InventoryController_getTransactionHistory","summary":"Get transaction history for a SKU","description":"The movement ledger for a SKU — every adjustment, reservation, sale, return, count and transfer, with the quantity before and after and the reference that caused it.\n\nThis is the audit trail: when stock does not match expectations, this is where the discrepancy is found.\n\n#### Signature\n\n```http\nGET /storefront/inventory/sku/{sku}/transactions (sku: string, locationId?: string, page?: integer, pageSize?: integer) -> A page of movements\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A `reserve` movement records `previousQuantity` and `newQuantity` as the same value — reserving moves stock between the reserved and available buckets without changing what is on hand.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory/adjust`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"sku","required":true,"in":"path","description":"Product SKU.","schema":{"type":"string"},"example":"DRK-COLA-330"},{"name":"locationId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one location.","example":"loc_downtown"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of movements","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"type":{"type":"string","description":"`adjustment`, `reserve`, `release`, `sale`, `return`, `count`, `transfer`.","example":"sale"},"quantity":{"type":"number","description":"Signed — negative for stock leaving.","example":-2},"previousQuantity":{"type":"number","example":122},"newQuantity":{"type":"number","example":120},"referenceType":{"type":"string","example":"order"},"referenceId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"}}}},"total":{"type":"integer","example":412}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory":{"post":{"operationId":"InventoryController_setInventory","summary":"Set inventory for a SKU at a location","description":"Creates or replaces the stock record for one SKU at one location. This is the endpoint that establishes a level in the first place — every other write requires the record to exist already.\n\nIt **sets** rather than adjusts, so it overwrites the current quantity outright. Use `POST /storefront/inventory/adjust` for a relative change with a recorded reason, and `POST /storefront/inventory/count` to reconcile against a physical count.\n\n#### Signature\n\n```http\nPOST /storefront/inventory (body) -> The stock level\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Overwrites rather than accumulates. Calling it twice with the same quantity leaves that quantity, not double.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory/adjust`\n- `POST /storefront/inventory/count`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The stock level","content":{"application/json":{"schema":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"The stock level to set. `sku` and `locationId` identify it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","locationId"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}},"example":{"sku":"DRK-COLA-330","locationId":"loc_downtown","quantity":120,"reorderPoint":25,"reorderQuantity":200}}}}}},"/storefront/inventory/adjust":{"post":{"operationId":"InventoryController_adjustInventory","summary":"Adjust inventory","description":"Applies a relative change to on-hand stock and records why. Send a positive `adjustment` to add stock and a negative one to remove it.\n\nA `reason` is required — an unexplained stock change is the thing that makes a ledger useless. Attach `referenceType` and `referenceId` to link the movement to whatever caused it.\n\nThe adjustment is refused if it would take stock below zero.\n\n#### Signature\n\n```http\nPOST /storefront/inventory/adjust (body) -> The updated stock level\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent: each call applies the change again.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVENTORY_NOT_FOUND | Inventory not found for <sku> at <locationId> | No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero. | Create the level first with `POST /storefront/inventory`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory/count`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The updated stock level","content":{"application/json":{"schema":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}},"400":{"description":"Inventory not found for <sku> at <locationId> — No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Inventory not found for <sku> at <locationId>","path":"/storefront/inventory/adjust","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"The change, and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","locationId","adjustment","reason"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","example":"loc_downtown"},"adjustment":{"type":"number","description":"Signed change. Negative removes stock.","example":-3},"reason":{"type":"string","description":"Recorded on the movement.","example":"Damaged in the stockroom"},"referenceType":{"type":"string","description":"What caused it, e.g. `order`, `return`, `stocktake`.","example":"stocktake"},"referenceId":{"type":"string","description":"Identifier of that thing.","example":"ST-2026-08"}}},"examples":{"shrinkage":{"summary":"Write off damaged stock","value":{"sku":"DRK-COLA-330","locationId":"loc_downtown","adjustment":-3,"reason":"Damaged in the stockroom"}},"delivery":{"summary":"Book in a delivery","value":{"sku":"DRK-COLA-330","locationId":"loc_downtown","adjustment":200,"reason":"Supplier delivery","referenceType":"purchase_order","referenceId":"PO-9912"}}}}}}}},"/storefront/inventory/reserve":{"post":{"operationId":"InventoryController_reserveInventory","summary":"Reserve inventory for an order","description":"Commits stock to an order without shipping it: `reservedQuantity` goes up and `availableQuantity` comes down by the same amount. **On-hand `quantity` does not change** — the goods are still on the shelf, just no longer sellable to anyone else.\n\nRefused when there is not enough available, and the error reports exactly how much there is.\n\nEvery reservation must eventually be released or fulfilled, or the stock stays committed forever.\n\n#### Signature\n\n```http\nPOST /storefront/inventory/reserve (body) -> The updated stock level\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent: a retry reserves the quantity a second time. Nothing links a reservation to its order beyond the ledger entry, so a duplicate cannot be detected automatically.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVENTORY_NOT_FOUND | Inventory not found for <sku> at <locationId> | No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero. | Create the level first with `POST /storefront/inventory`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory/release`\n- `POST /storefront/inventory/fulfill`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The updated stock level","content":{"application/json":{"schema":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}},"400":{"description":"Inventory not found for <sku> at <locationId> — No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Inventory not found for <sku> at <locationId>","path":"/storefront/inventory/reserve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"What to reserve, and for which order.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","locationId","quantity","orderId"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","example":"loc_downtown"},"quantity":{"type":"number","example":2},"orderId":{"type":"string","description":"Order this movement belongs to. Recorded on the transaction.","example":"66f1a2b3c4d5e6f708192a3b"}}},"example":{"sku":"DRK-COLA-330","locationId":"loc_downtown","quantity":2,"orderId":"66f1a2b3c4d5e6f708192a3b"}}}}}},"/storefront/inventory/release":{"post":{"operationId":"InventoryController_releaseReservation","summary":"Release a reservation","description":"Undoes a reservation when an order is cancelled: `reservedQuantity` comes down and `availableQuantity` goes back up. On-hand quantity is untouched.\n\nCall this whenever a reserved order does not ship, or the stock stays committed and invisible to other customers.\n\n#### Signature\n\n```http\nPOST /storefront/inventory/release (body) -> The updated stock level\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Releasing more than was reserved is not rejected — reserved stock is floored at zero, which silently inflates available stock. Release exactly what you reserved.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVENTORY_NOT_FOUND | Inventory not found for <sku> at <locationId> | No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero. | Create the level first with `POST /storefront/inventory`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory/reserve`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The updated stock level","content":{"application/json":{"schema":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}},"400":{"description":"Inventory not found for <sku> at <locationId> — No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Inventory not found for <sku> at <locationId>","path":"/storefront/inventory/release","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"What to release, and from which order.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","locationId","quantity","orderId"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","example":"loc_downtown"},"quantity":{"type":"number","example":2},"orderId":{"type":"string","description":"Order this movement belongs to. Recorded on the transaction.","example":"66f1a2b3c4d5e6f708192a3b"}}},"example":{"sku":"DRK-COLA-330","locationId":"loc_downtown","quantity":2,"orderId":"66f1a2b3c4d5e6f708192a3b"}}}}}},"/storefront/inventory/fulfill":{"post":{"operationId":"InventoryController_fulfillInventory","summary":"Fulfill inventory after shipment","description":"Takes stock off the shelf once an order ships. On-hand `quantity` comes down by the shipped amount, the matching reservation is cleared, and `availableQuantity` is recomputed.\n\nThis is the step that makes a sale permanent — the movement is recorded as type `sale`.\n\nRefused if it would take on-hand stock below zero. The check runs before anything is written, so a rejected fulfilment leaves the record untouched.\n\n#### Signature\n\n```http\nPOST /storefront/inventory/fulfill (body) -> The updated stock level\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Fulfilling clears the reservation as part of the same call — do not also release it, or available stock will be overstated.\n- Reserved quantity is floored at zero, so fulfilling stock that was never reserved still works and simply reduces on-hand.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVENTORY_NOT_FOUND | Inventory not found for <sku> at <locationId> | No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero. | Create the level first with `POST /storefront/inventory`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory/reserve`\n- `POST /storefront/inventory/return`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The updated stock level","content":{"application/json":{"schema":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}},"400":{"description":"Inventory not found for <sku> at <locationId> — No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Inventory not found for <sku> at <locationId>","path":"/storefront/inventory/fulfill","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"What shipped, and for which order.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","locationId","quantity","orderId"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","example":"loc_downtown"},"quantity":{"type":"number","example":2},"orderId":{"type":"string","description":"Order this movement belongs to. Recorded on the transaction.","example":"66f1a2b3c4d5e6f708192a3b"}}},"example":{"sku":"DRK-COLA-330","locationId":"loc_downtown","quantity":2,"orderId":"66f1a2b3c4d5e6f708192a3b"}}}}}},"/storefront/inventory/return":{"post":{"operationId":"InventoryController_returnToStock","summary":"Return stock from a return","description":"Books returned goods back in. **`condition` decides whether stock actually moves:**\n\n- `restockable` — on-hand and available both go up by `quantity`.\n- `damaged` — **no stock change at all.** The movement is still recorded, so the return is auditable, but the goods are written off rather than made sellable.\n\nThat asymmetry is easy to miss: a `200` on a `damaged` return does not mean anything was added.\n\n#### Signature\n\n```http\nPOST /storefront/inventory/return (body) -> The stock level — unchanged when the condition was `damaged`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVENTORY_NOT_FOUND | Inventory not found for <sku> at <locationId> | No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero. | Create the level first with `POST /storefront/inventory`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/returns/{rmaNumber}/complete`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The stock level — unchanged when the condition was `damaged`","content":{"application/json":{"schema":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}}}}},"400":{"description":"Inventory not found for <sku> at <locationId> — No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Inventory not found for <sku> at <locationId>","path":"/storefront/inventory/return","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"What came back, from which return, and in what state.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","locationId","quantity","returnId","condition"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","example":"loc_downtown"},"quantity":{"type":"number","example":2},"returnId":{"type":"string","description":"RMA the goods came back under.","example":"RMA-4821"},"condition":{"type":"string","enum":["restockable","damaged"],"description":"`damaged` records the movement without changing stock.","example":"restockable"}}},"examples":{"restock":{"summary":"Goods fit to resell","value":{"sku":"DRK-COLA-330","locationId":"loc_downtown","quantity":2,"returnId":"RMA-4821","condition":"restockable"}},"writeOff":{"summary":"Damaged goods","description":"Recorded for audit; stock is not increased.","value":{"sku":"DRK-COLA-330","locationId":"loc_downtown","quantity":2,"returnId":"RMA-4821","condition":"damaged"}}}}}}}},"/storefront/inventory/count":{"post":{"operationId":"InventoryController_performCount","summary":"Record a physical count","description":"Reconciles the system against a physical stocktake. On-hand quantity is **set** to `countedQuantity`, `availableQuantity` is recomputed from it, and `lastCountDate` is stamped.\n\nThe response includes the `variance` — counted minus previous — which is the number a stocktake report actually cares about. A negative variance is shrinkage.\n\nReserved quantity is left alone: a count measures what is on the shelf, not what is promised to orders.\n\n#### Signature\n\n```http\nPOST /storefront/inventory/count (body) -> The reconciled level and the variance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A count can take available stock negative if more is reserved than was counted — the count is trusted as the truth and is not clamped.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVENTORY_NOT_FOUND | Inventory not found for <sku> at <locationId> | No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero. | Create the level first with `POST /storefront/inventory`. Note this is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/inventory/adjust`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The reconciled level and the variance","content":{"application/json":{"schema":{"type":"object","properties":{"inventory":{"type":"object","description":"An inventory level (`sf_inventory`) for one SKU at one location.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","description":"Warehouse or store this level belongs to.","example":"loc_downtown"},"quantity":{"type":"number","description":"Physically on hand, including units reserved for orders.","example":120},"reservedQuantity":{"type":"number","description":"Committed to orders but not yet shipped.","example":20},"availableQuantity":{"type":"number","description":"`quantity` minus `reservedQuantity` — what can still be sold.","example":100},"reorderPoint":{"type":"number","description":"Level at or below which the SKU appears in low-stock alerts.","example":25},"reorderQuantity":{"type":"number","description":"Suggested restock amount.","example":200},"lastCountDate":{"type":"string","format":"date-time","description":"When a physical count was last recorded."}}}}},"variance":{"type":"number","description":"`countedQuantity` minus the previous on-hand. Negative means stock was missing.","example":-3}}},"example":{"inventory":{"data":{"sku":"DRK-COLA-330","quantity":117,"reservedQuantity":20,"availableQuantity":97}},"variance":-3}}}},"400":{"description":"Inventory not found for <sku> at <locationId> — No inventory record exists for that SKU at that location. A SKU that has never been stocked at a location has no record — it is not treated as zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Inventory not found for <sku> at <locationId>","path":"/storefront/inventory/count","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"What was counted.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","locationId","countedQuantity"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"locationId":{"type":"string","example":"loc_downtown"},"countedQuantity":{"type":"number","description":"Units physically present. Becomes the new on-hand quantity.","example":117},"notes":{"type":"string","description":"Recorded on the movement.","example":"Quarterly stocktake"}}},"example":{"sku":"DRK-COLA-330","locationId":"loc_downtown","countedQuantity":117,"notes":"Quarterly stocktake"}}}}}},"/storefront/inventory/transfers":{"post":{"operationId":"InventoryController_createTransfer","summary":"Create an inventory transfer","description":"Opens a transfer of stock between two locations. The transfer starts `pending`.\n\nSource stock is checked up front: if any line exceeds what the source location holds, the whole transfer is refused rather than created partially.\n\n#### Signature\n\n```http\nPOST /storefront/inventory/transfers (body) -> The created transfer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INSUFFICIENT_SOURCE_INVENTORY | Insufficient inventory for <sku> at source location | A line asks for more than the source location holds. | Check source levels first. The whole transfer is refused — no partial transfer is created. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/inventory/transfers/{transferId}/ship`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The created transfer","content":{"application/json":{"schema":{"type":"object","description":"An inventory transfer (`sf_inventory_transfer`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"transferId":{"type":"string","example":"TR-4821"},"status":{"type":"string","enum":["pending","in_transit","completed","cancelled"],"example":"pending"},"fromLocationId":{"type":"string","example":"loc_warehouse"},"fromLocationName":{"type":"string","example":"Central warehouse"},"toLocationId":{"type":"string","example":"loc_downtown"},"toLocationName":{"type":"string","example":"Downtown store"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"productName":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"Quantity sent.","example":48},"receivedQuantity":{"type":"number","description":"Quantity confirmed on receipt. Set at the receiving end.","example":46}}}},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"carrier":{"type":"string","example":"ups"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Insufficient inventory for <sku> at source location — A line asks for more than the source location holds.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Insufficient inventory for <sku> at source location","path":"/storefront/inventory/transfers","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"Where the stock is going, and what.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["fromLocationId","toLocationId","items"],"properties":{"fromLocationId":{"type":"string","example":"loc_warehouse"},"fromLocationName":{"type":"string","description":"Display name, stored for reporting.","example":"Central warehouse"},"toLocationId":{"type":"string","example":"loc_downtown"},"toLocationName":{"type":"string","example":"Downtown store"},"items":{"type":"array","items":{"type":"object","required":["sku","quantity"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"productName":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":48}}}},"notes":{"type":"string","example":"Weekly replenishment"}}},"example":{"fromLocationId":"loc_warehouse","toLocationId":"loc_downtown","items":[{"sku":"DRK-COLA-330","productName":"Cola 330ml","quantity":48}],"notes":"Weekly replenishment"}}}}},"get":{"operationId":"InventoryController_listTransfers","summary":"List inventory transfers","description":"Lists transfers with optional filters and paging. Filter by `status=in_transit` for stock currently on the road.\n\n#### Signature\n\n```http\nGET /storefront/inventory/transfers (status?: string, fromLocationId?: string, toLocationId?: string, page?: integer, pageSize?: integer) -> A page of transfers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/inventory/transfers/{transferId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["pending","in_transit","completed","cancelled"]},"example":"in_transit"},{"name":"fromLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"loc_warehouse"},{"name":"toLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"loc_downtown"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of transfers","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"An inventory transfer (`sf_inventory_transfer`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"transferId":{"type":"string","example":"TR-4821"},"status":{"type":"string","enum":["pending","in_transit","completed","cancelled"],"example":"pending"},"fromLocationId":{"type":"string","example":"loc_warehouse"},"fromLocationName":{"type":"string","example":"Central warehouse"},"toLocationId":{"type":"string","example":"loc_downtown"},"toLocationName":{"type":"string","example":"Downtown store"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"productName":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"Quantity sent.","example":48},"receivedQuantity":{"type":"number","description":"Quantity confirmed on receipt. Set at the receiving end.","example":46}}}},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"carrier":{"type":"string","example":"ups"},"notes":{"type":"string"}}}}}},"total":{"type":"integer","example":8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory/transfers/{transferId}":{"get":{"operationId":"InventoryController_getTransfer","summary":"Get an inventory transfer","description":"Fetches one transfer with its lines, including the received quantities once it has been receipted.\n\n#### Signature\n\n```http\nGET /storefront/inventory/transfers/{transferId} (transferId: string) -> The transfer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TRANSFER_NOT_FOUND | Transfer <transferId> not found | No transfer in the org has that id. | Check the id with `GET /storefront/inventory/transfers`. This is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/inventory/transfers`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"transferId","required":true,"in":"path","description":"Transfer id.","schema":{"type":"string"},"example":"TR-4821"}],"responses":{"200":{"description":"The transfer","content":{"application/json":{"schema":{"type":"object","description":"An inventory transfer (`sf_inventory_transfer`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"transferId":{"type":"string","example":"TR-4821"},"status":{"type":"string","enum":["pending","in_transit","completed","cancelled"],"example":"pending"},"fromLocationId":{"type":"string","example":"loc_warehouse"},"fromLocationName":{"type":"string","example":"Central warehouse"},"toLocationId":{"type":"string","example":"loc_downtown"},"toLocationName":{"type":"string","example":"Downtown store"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"productName":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"Quantity sent.","example":48},"receivedQuantity":{"type":"number","description":"Quantity confirmed on receipt. Set at the receiving end.","example":46}}}},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"carrier":{"type":"string","example":"ups"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Transfer <transferId> not found — No transfer in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Transfer <transferId> not found","path":"/storefront/inventory/transfers/{transferId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"]}},"/storefront/inventory/transfers/{transferId}/ship":{"put":{"operationId":"InventoryController_shipTransfer","summary":"Ship an inventory transfer","description":"Marks a pending transfer as dispatched and records the carrier and tracking. The transfer moves to `in_transit` and the stock leaves the source location.\n\nOnly a `pending` transfer can be shipped.\n\n#### Signature\n\n```http\nPUT /storefront/inventory/transfers/{transferId}/ship (transferId: string, body) -> The shipped transfer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TRANSFER_NOT_FOUND | Transfer <transferId> not found | No transfer in the org has that id. | Check the id with `GET /storefront/inventory/transfers`. This is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/inventory/transfers/{transferId}/receive`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"transferId","required":true,"in":"path","description":"Transfer id.","schema":{"type":"string"},"example":"TR-4821"}],"responses":{"200":{"description":"The shipped transfer","content":{"application/json":{"schema":{"type":"object","description":"An inventory transfer (`sf_inventory_transfer`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"transferId":{"type":"string","example":"TR-4821"},"status":{"type":"string","enum":["pending","in_transit","completed","cancelled"],"example":"pending"},"fromLocationId":{"type":"string","example":"loc_warehouse"},"fromLocationName":{"type":"string","example":"Central warehouse"},"toLocationId":{"type":"string","example":"loc_downtown"},"toLocationName":{"type":"string","example":"Downtown store"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"productName":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"Quantity sent.","example":48},"receivedQuantity":{"type":"number","description":"Quantity confirmed on receipt. Set at the receiving end.","example":46}}}},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"carrier":{"type":"string","example":"ups"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Transfer <transferId> not found — No transfer in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Transfer <transferId> not found","path":"/storefront/inventory/transfers/{transferId}/ship","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"How the stock was dispatched.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["trackingNumber","carrier"],"properties":{"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"carrier":{"type":"string","example":"ups"}}},"example":{"trackingNumber":"1Z999AA10123456784","carrier":"ups"}}}}}},"/storefront/inventory/transfers/{transferId}/receive":{"put":{"operationId":"InventoryController_receiveTransfer","summary":"Receive an inventory transfer","description":"Books a transfer in at the destination. The received quantities are recorded per line and added to the destination's stock, and the transfer moves to `completed`.\n\n**Receive what actually arrived, not what was sent.** `receivedQuantity` can be lower than the shipped quantity, and the difference is the shrinkage in transit — which the transfer record then preserves as evidence.\n\nOnly an `in_transit` transfer can be received.\n\n#### Signature\n\n```http\nPUT /storefront/inventory/transfers/{transferId}/receive (transferId: string, body) -> The completed transfer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A line omitted from `receivedItems` is not booked in — send an entry for every line, using `0` for one that did not arrive.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TRANSFER_NOT_FOUND | Transfer <transferId> not found | No transfer in the org has that id. | Check the id with `GET /storefront/inventory/transfers`. This is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/inventory/transfers/{transferId}/ship`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"transferId","required":true,"in":"path","description":"Transfer id.","schema":{"type":"string"},"example":"TR-4821"}],"responses":{"200":{"description":"The completed transfer","content":{"application/json":{"schema":{"type":"object","description":"An inventory transfer (`sf_inventory_transfer`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"transferId":{"type":"string","example":"TR-4821"},"status":{"type":"string","enum":["pending","in_transit","completed","cancelled"],"example":"pending"},"fromLocationId":{"type":"string","example":"loc_warehouse"},"fromLocationName":{"type":"string","example":"Central warehouse"},"toLocationId":{"type":"string","example":"loc_downtown"},"toLocationName":{"type":"string","example":"Downtown store"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"productName":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"Quantity sent.","example":48},"receivedQuantity":{"type":"number","description":"Quantity confirmed on receipt. Set at the receiving end.","example":46}}}},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"carrier":{"type":"string","example":"ups"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Transfer <transferId> not found — No transfer in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Transfer <transferId> not found","path":"/storefront/inventory/transfers/{transferId}/receive","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"What actually arrived, per line.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["receivedItems"],"properties":{"receivedItems":{"type":"array","description":"One entry per line, matched by `sku`.","items":{"type":"object","required":["sku","receivedQuantity"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"receivedQuantity":{"type":"number","description":"Units actually received.","example":46}}}}}},"examples":{"full":{"summary":"Everything arrived","value":{"receivedItems":[{"sku":"DRK-COLA-330","receivedQuantity":48}]}},"short":{"summary":"Two units short","description":"The shortfall stays visible on the transfer record.","value":{"receivedItems":[{"sku":"DRK-COLA-330","receivedQuantity":46}]}}}}}}}},"/storefront/inventory/transfers/{transferId}/cancel":{"put":{"operationId":"InventoryController_cancelTransfer","summary":"Cancel an inventory transfer","description":"Cancels a transfer that has not yet shipped, returning the committed stock to the source location.\n\n**Only a `pending` transfer can be cancelled.** Once stock is in transit it physically exists somewhere between two locations, so it must be received — short if necessary — rather than cancelled.\n\n#### Signature\n\n```http\nPUT /storefront/inventory/transfers/{transferId}/cancel (transferId: string, body) -> The cancelled transfer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TRANSFER_NOT_FOUND | Transfer <transferId> not found | No transfer in the org has that id. | Check the id with `GET /storefront/inventory/transfers`. This is a `400`, not a `404`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/inventory/transfers/{transferId}/receive`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"transferId","required":true,"in":"path","description":"Transfer id.","schema":{"type":"string"},"example":"TR-4821"}],"responses":{"200":{"description":"The cancelled transfer","content":{"application/json":{"schema":{"type":"object","description":"An inventory transfer (`sf_inventory_transfer`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"transferId":{"type":"string","example":"TR-4821"},"status":{"type":"string","enum":["pending","in_transit","completed","cancelled"],"example":"pending"},"fromLocationId":{"type":"string","example":"loc_warehouse"},"fromLocationName":{"type":"string","example":"Central warehouse"},"toLocationId":{"type":"string","example":"loc_downtown"},"toLocationName":{"type":"string","example":"Downtown store"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"productName":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","description":"Quantity sent.","example":48},"receivedQuantity":{"type":"number","description":"Quantity confirmed on receipt. Set at the receiving end.","example":46}}}},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"carrier":{"type":"string","example":"ups"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Transfer <transferId> not found — No transfer in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Transfer <transferId> not found","path":"/storefront/inventory/transfers/{transferId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Inventory"],"requestBody":{"description":"Why the transfer is being cancelled.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Recorded on the transfer.","example":"Store no longer needs the stock"}}},"example":{"reason":"Store no longer needs the stock"}}}}}},"/storefront/discounts/validate":{"post":{"operationId":"DiscountController_validate","summary":"Validate a discount code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Whether the code applies, and why not when it does not","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","example":true},"reason":{"type":"string","nullable":true,"enum":["not_found","inactive","not_started","expired","usage_limit","customer_group","customer_group_excluded","customer_not_allowed","customer_excluded","email_required","first_order_only","min_cart_value","min_cart_items","required_products"],"description":"Machine-readable rejection code. Absent when `valid` is true."},"message":{"type":"string","description":"Shopper-facing explanation. Display this; do not parse it.","example":"Minimum cart value of $50 required"},"discount":{"type":"object","description":"The matched discount, when valid.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}},"examples":{"valid":{"summary":"Code applies","value":{"valid":true,"discount":{"data":{"code":"SUMMER20","type":"percent","value":20}}}},"rejected":{"summary":"Below the minimum","value":{"valid":false,"reason":"min_cart_value","message":"Minimum cart value of $50 required"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Checks whether a code can be used by the calling customer for a given cart, and says precisely why not when it cannot.\n\n**A rejection is a `200`, not an error.** The response carries `valid: false` with a machine-readable `reason` and a message written for the shopper. Branch on `reason`; the `message` text is for display and may change.\n\n| `reason` | Meaning |\n| --- | --- |\n| `not_found` | No discount in the org has that code. |\n| `inactive` | The discount exists but its status is not active. |\n| `not_started` | The discount's start date is in the future. |\n| `expired` | The discount's end date has passed. |\n| `usage_limit` | The discount has been redeemed as many times as it allows. |\n| `customer_group` | The customer is not in a group the discount targets. |\n| `customer_group_excluded` | The customer is in a group the discount excludes. |\n| `customer_not_allowed` | The discount names specific customers and this is not one of them. |\n| `customer_excluded` | The discount excludes this specific customer. |\n| `email_required` | The discount needs an email to evaluate and none was available. |\n| `first_order_only` | The discount is for first-time customers and this one has ordered before. |\n| `min_cart_value` | The cart subtotal is below the discount's minimum. |\n| `min_cart_items` | The cart has fewer items than the discount requires. |\n| `required_products` | The cart does not contain the products the discount requires. |\n\nThe email used for first-order and email-gated checks comes from the signed-in customer when there is one, falling back to the `email` in the body.\n\nThis validates only — it records no usage and changes nothing.\n\n#### Signature\n\n```http\nPOST /storefront/discounts/validate (body) -> Whether the code applies, and why not when it does not\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The four `customer_*` reasons all share the message \"Discount not available for your account\" — they are only distinguishable by `reason`.\n- Validation does not reserve or record anything. A code valid here can still be exhausted by someone else before checkout.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/discounts/apply`\n- `POST /storefront/discounts/{identifier}/usage`","requestBody":{"description":"The code and the cart to test it against.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"The code to validate.","example":"SUMMER20"},"subtotal":{"type":"number","description":"Cart subtotal, for the minimum-value check.","example":129.99},"productItems":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}},"description":"Ordinary product lines."},"rentalItems":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Rental lines."},"shippingAddress":{"type":"object","additionalProperties":true,"description":"Used where a discount depends on destination."},"email":{"type":"string","description":"Used only when no customer is signed in.","example":"ada@example.com"},"cartId":{"type":"string","description":"Cart the code is being tested for."}}},"examples":{"simple":{"summary":"Check a code against a subtotal","value":{"code":"SUMMER20","subtotal":129.99}},"withCart":{"summary":"Check against real lines","description":"Needed for the item-count and required-product rules.","value":{"code":"SUMMER20","productItems":[{"sku":"DRK-COLA-330","quantity":2,"price":12}]}}}}}}}},"/storefront/discounts/promotions/active":{"get":{"operationId":"DiscountController_getActivePromotions","summary":"List active promotions for display","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active coded promotions, projected for display","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","example":"percent"},"value":{"type":"number","example":20},"minCartValue":{"type":"number","example":50},"endDate":{"type":"string","format":"date-time","example":"2026-08-31T23:59:59.000Z"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"A shopper-safe list of the active, **code-based** promotions — the \"current offers\" strip on a storefront.\n\nThe response is deliberately a projection, not the discount records: only `code`, `name`, `description`, `type`, `value`, `minCartValue` and `endDate` are exposed, so targeting rules, usage limits and customer restrictions are never leaked to the browser.\n\nAuto-apply discounts are excluded — they have no code to advertise.\n\n#### Signature\n\n```http\nGET /storefront/discounts/promotions/active () -> Active coded promotions, projected for display\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Listing a promotion here does not mean a given shopper can use it — group and customer targeting still apply. Validate before promising it.\n- Unpaged: every active coded promotion is returned.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/discounts/validate`"}},"/storefront/discounts/automatic":{"post":{"operationId":"DiscountController_getAutomaticDiscounts","summary":"Get automatic discounts for a cart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The automatic discounts that qualify","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Returns the code-less discounts that would apply to this cart for the calling customer, without pricing the cart. Use it to show \"you are getting 10% off\" before the shopper reaches checkout.\n\n#### Signature\n\n```http\nPOST /storefront/discounts/automatic (body) -> The automatic discounts that qualify\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only discounts with `autoApply: true` are considered. Coded promotions never appear here.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/discounts/promotions/active`","requestBody":{"description":"The cart to evaluate.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subtotal":{"type":"number","example":129.99},"productItems":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}},"description":"Ordinary product lines."},"rentalItems":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Rental lines."},"shippingAddress":{"type":"object","additionalProperties":true,"description":"Used where a discount depends on destination."}}},"example":{"subtotal":129.99,"productItems":[{"sku":"DRK-COLA-330","quantity":2,"price":12}]}}}}}},"/storefront/discounts/apply":{"post":{"operationId":"DiscountController_applyCoupon","summary":"Apply a coupon and price the cart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The priced cart, with applied discounts and any coupon rejection reason","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Prices a cart with a coupon applied, together with every automatic discount that qualifies. This is the call a checkout page makes when the shopper enters a code.\n\nThe code may be sent as either `couponCode` or `code` — `couponCode` wins when both are present.\n\nAn invalid code does not fail the request: the cart is priced without it and the reason is reported in the response, so the page can show the total *and* explain why the code did not take.\n\n#### Signature\n\n```http\nPOST /storefront/discounts/apply (body) -> The priced cart, with applied discounts and any coupon rejection reason\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Pricing only — nothing is redeemed. Record the redemption at order time with `POST /storefront/discounts/{identifier}/usage`.\n- Identical to `POST /storefront/discounts/calculate` except that this one accepts the `code` alias.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/discounts/calculate`\n- `POST /storefront/discounts/validate`","requestBody":{"description":"The cart and the coupon.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"couponCode":{"type":"string","description":"The code to apply. Takes precedence over `code`.","example":"SUMMER20"},"code":{"type":"string","description":"Alias for `couponCode`, accepted for older clients."},"productItems":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}},"description":"Ordinary product lines."},"rentalItems":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Rental lines."},"shippingAddress":{"type":"object","additionalProperties":true,"description":"Used where a discount depends on destination."}}},"example":{"couponCode":"SUMMER20","productItems":[{"sku":"DRK-COLA-330","quantity":2,"price":12}]}}}}}},"/storefront/discounts/calculate":{"post":{"operationId":"DiscountController_calculateCart","summary":"Calculate a cart with all discounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The priced cart with all discounts resolved","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Prices a cart with every applicable discount — automatic ones always, plus `couponCode` when supplied.\n\nThis is the same calculation `POST /storefront/pricing/calculate-cart` performs; that endpoint delegates here so cart discounting has one implementation. The only difference from `apply` is that this one does not accept the legacy `code` alias.\n\n#### Signature\n\n```http\nPOST /storefront/discounts/calculate (body) -> The priced cart with all discounts resolved\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/discounts/apply`\n- `POST /storefront/pricing/calculate-cart`","requestBody":{"description":"The cart to price.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"couponCode":{"type":"string","example":"SUMMER20"},"productItems":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","example":2},"price":{"type":"number","description":"Unit price.","example":12}}},"description":"Ordinary product lines."},"rentalItems":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Rental lines."},"shippingAddress":{"type":"object","additionalProperties":true,"description":"Used where a discount depends on destination."}}},"example":{"productItems":[{"sku":"DRK-COLA-330","quantity":2,"price":12}],"couponCode":"SUMMER20"}}}}}},"/storefront/discounts":{"post":{"operationId":"DiscountController_create","summary":"Create a discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created discount","content":{"application/json":{"schema":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}}},"400":{"description":"Discount code <code> already exists — Another discount in the org already uses that code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount code <code> already exists","path":"/storefront/discounts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Creates a discount or an automatic promotion. Omit `code` and set `autoApply` for one that applies without the shopper typing anything.\n\nCodes are unique: creating one that already exists is rejected.\n\n#### Signature\n\n```http\nPOST /storefront/discounts (body) -> The created discount\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CODE_EXISTS | Discount code <code> already exists | Another discount in the org already uses that code. | Choose a different code, or update the existing discount instead. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/discounts/{identifier}`\n- `GET /storefront/discounts`","requestBody":{"description":"The discount to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}},"examples":{"percentCode":{"summary":"A 20% coupon with limits","value":{"code":"SUMMER20","name":"Summer sale","type":"percent","value":20,"status":"active","minCartValue":50,"usageLimit":500,"endDate":"2026-08-31T23:59:59.000Z"}},"automatic":{"summary":"An automatic discount, no code","value":{"name":"Bulk discount","type":"percent","value":10,"status":"active","autoApply":true,"minCartItems":12}}}}}}},"get":{"operationId":"DiscountController_list","summary":"List discounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by status, e.g. `active`.","example":"active"},{"name":"hasCode","required":false,"in":"query","description":"`true` for code-based discounts, `false` for auto-apply. Omit for both.","schema":{"type":"string","enum":["true","false"]},"example":"true"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of discounts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}},"total":{"type":"integer","example":12}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Lists discounts with optional filtering and paging.\n\n**Mind the `hasCode` inversion.** `hasCode=true` returns code-based discounts and `hasCode=false` returns automatic ones — the parameter is translated to the internal `autoApply` flag, inverted. Omitting it returns both. Any value other than the exact strings `true` and `false` is treated as omitted.\n\n#### Signature\n\n```http\nGET /storefront/discounts (status?: string, hasCode?: string, page?: integer, pageSize?: integer) -> A page of discounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Unlike the promotions endpoint, this returns full records including targeting and usage — it is an operator read, not a shopper one.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/discounts/promotions/active`"}},"/storefront/discounts/stats":{"get":{"operationId":"DiscountController_getStats","summary":"Get discount statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Aggregate discount statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Aggregate usage across the org's discounts — how many exist, how many are active, and how much has been redeemed. The operator overview.\n\n#### Signature\n\n```http\nGET /storefront/discounts/stats () -> Aggregate discount statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Declared before `GET /storefront/discounts/{identifier}` on the controller, so `stats` resolves as the statistics route and can never be read as a discount named \"stats\".\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/discounts`"}},"/storefront/discounts/{identifier}":{"get":{"operationId":"DiscountController_getByIdentifier","summary":"Get a discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"identifier","required":true,"in":"path","schema":{"type":"string"},"description":"Discount `code` **or** record id — both resolve.","example":"SUMMER20"}],"responses":{"200":{"description":"The discount","content":{"application/json":{"schema":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}}},"400":{"description":"Discount <identifier> not found — No discount matches that code or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount <identifier> not found","path":"/storefront/discounts/{identifier}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Fetches one discount by its code or its record id — both resolve through the same lookup.\n\n#### Signature\n\n```http\nGET /storefront/discounts/{identifier} (identifier: string) -> The discount\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DISCOUNT_NOT_FOUND | Discount <identifier> not found | No discount matches that code or id. | List discounts with `GET /storefront/discounts`. Note this is a `400`, not a `404` — do not branch on the status alone. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/discounts/{identifier}`"},"put":{"operationId":"DiscountController_update","summary":"Update a discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"identifier","required":true,"in":"path","schema":{"type":"string"},"description":"Discount `code` **or** record id — both resolve.","example":"SUMMER20"}],"responses":{"200":{"description":"The updated discount","content":{"application/json":{"schema":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}}},"400":{"description":"Discount <identifier> not found — No discount matches that code or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount <identifier> not found","path":"/storefront/discounts/{identifier}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Merges the body into an existing discount. Fields you omit keep their current values; arrays you send replace their counterparts entirely.\n\n#### Signature\n\n```http\nPUT /storefront/discounts/{identifier} (identifier: string, body) -> The updated discount\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Changing `value` or `type` affects only future redemptions — orders already discounted keep what they were given.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DISCOUNT_NOT_FOUND | Discount <identifier> not found | No discount matches that code or id. | List discounts with `GET /storefront/discounts`. Note this is a `400`, not a `404` — do not branch on the status alone. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/discounts/{identifier}/activate`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}},"examples":{"extend":{"summary":"Extend the end date","value":{"endDate":"2026-09-30T23:59:59.000Z"}},"raiseLimit":{"summary":"Raise the usage limit","value":{"usageLimit":1000}}}}}}},"delete":{"operationId":"DiscountController_delete","summary":"Delete a discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"identifier","required":true,"in":"path","schema":{"type":"string"},"description":"Discount `code` **or** record id — both resolve.","example":"SUMMER20"}],"responses":{"200":{"description":"Confirmation of the delete","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true}}},"example":{"success":true}}}},"400":{"description":"Discount <identifier> not found — No discount matches that code or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount <identifier> not found","path":"/storefront/discounts/{identifier}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Deletes a discount that has never been redeemed.\n\n**A used discount cannot be deleted** — the redemption history is evidence of what customers were charged, and removing the discount would orphan it. Deactivate instead.\n\n#### Signature\n\n```http\nDELETE /storefront/discounts/{identifier} (identifier: string) -> Confirmation of the delete\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DISCOUNT_NOT_FOUND | Discount <identifier> not found | No discount matches that code or id. | List discounts with `GET /storefront/discounts`. Note this is a `400`, not a `404` — do not branch on the status alone. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/discounts/{identifier}/deactivate`"}},"/storefront/discounts/{identifier}/activate":{"put":{"operationId":"DiscountController_activate","summary":"Activate a discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"identifier","required":true,"in":"path","schema":{"type":"string"},"description":"Discount `code` **or** record id — both resolve.","example":"SUMMER20"}],"responses":{"200":{"description":"The activated discount","content":{"application/json":{"schema":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}}},"400":{"description":"Discount <identifier> not found — No discount matches that code or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount <identifier> not found","path":"/storefront/discounts/{identifier}/activate","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Sets the discount status to active so it validates and applies. Start and end dates still gate it — activating a discount whose window has passed does not make it usable.\n\n#### Signature\n\n```http\nPUT /storefront/discounts/{identifier}/activate (identifier: string) -> The activated discount\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DISCOUNT_NOT_FOUND | Discount <identifier> not found | No discount matches that code or id. | List discounts with `GET /storefront/discounts`. Note this is a `400`, not a `404` — do not branch on the status alone. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/discounts/{identifier}/deactivate`"}},"/storefront/discounts/{identifier}/deactivate":{"put":{"operationId":"DiscountController_deactivate","summary":"Deactivate a discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"identifier","required":true,"in":"path","schema":{"type":"string"},"description":"Discount `code` **or** record id — both resolve.","example":"SUMMER20"}],"responses":{"200":{"description":"The deactivated discount","content":{"application/json":{"schema":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}}},"400":{"description":"Discount <identifier> not found — No discount matches that code or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount <identifier> not found","path":"/storefront/discounts/{identifier}/deactivate","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Takes a discount out of service. It stops validating immediately, with `reason: \"inactive\"`.\n\nThis is the reversible way to stop a promotion — prefer it to deleting, which also loses the usage history.\n\n#### Signature\n\n```http\nPUT /storefront/discounts/{identifier}/deactivate (identifier: string) -> The deactivated discount\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DISCOUNT_NOT_FOUND | Discount <identifier> not found | No discount matches that code or id. | List discounts with `GET /storefront/discounts`. Note this is a `400`, not a `404` — do not branch on the status alone. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/discounts/{identifier}/activate`\n- `DELETE /storefront/discounts/{identifier}`"}},"/storefront/discounts/{identifier}/usage":{"post":{"operationId":"DiscountController_recordUsage","summary":"Record a discount redemption","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"identifier","required":true,"in":"path","schema":{"type":"string"},"description":"Discount `code` **or** record id — both resolve.","example":"SUMMER20"}],"responses":{"201":{"description":"The updated discount, with its incremented usage","content":{"application/json":{"schema":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}}},"400":{"description":"Discount <identifier> not found — No discount matches that code or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount <identifier> not found","path":"/storefront/discounts/{identifier}/usage","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Records that a discount was actually used on an order, incrementing its usage count towards `usageLimit`.\n\nValidation and cart pricing never do this — a code can be validated any number of times without consuming it. Call this once the order is created, or the usage limit will never be reached.\n\nIt is **not idempotent**: calling it twice for one order counts two redemptions.\n\n#### Signature\n\n```http\nPOST /storefront/discounts/{identifier}/usage (identifier: string, body) -> The updated discount, with its incremented usage\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent. Guard against double submission, or reverse the surplus with the reverse endpoint.\n- Omitting `customerEmail` leaves the first-order-only rule unable to recognise this customer later.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DISCOUNT_NOT_FOUND | Discount <identifier> not found | No discount matches that code or id. | List discounts with `GET /storefront/discounts`. Note this is a `400`, not a `404` — do not branch on the status alone. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/discounts/{identifier}/reverse`","requestBody":{"description":"The order the discount was used on.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderId","orderNumber","discountAmount"],"properties":{"orderId":{"type":"string","description":"Order record id.","example":"66f1a2b3c4d5e6f708192a3b"},"orderNumber":{"type":"string","description":"Public order number.","example":"A7K2M9QX4"},"discountAmount":{"type":"number","description":"How much was actually discounted.","example":26},"customerEmail":{"type":"string","description":"Who redeemed it. Used by the first-order-only rule.","example":"ada@example.com"}}},"example":{"orderId":"66f1a2b3c4d5e6f708192a3b","orderNumber":"A7K2M9QX4","discountAmount":26,"customerEmail":"ada@example.com"}}}}}},"/storefront/discounts/{identifier}/reverse":{"post":{"operationId":"DiscountController_reverseUsage","summary":"Reverse a discount redemption","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"identifier","required":true,"in":"path","schema":{"type":"string"},"description":"Discount `code` **or** record id — both resolve.","example":"SUMMER20"}],"responses":{"201":{"description":"The updated discount","content":{"application/json":{"schema":{"type":"object","description":"A discount (`sf_discount`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","description":"Redemption code. Absent on an auto-apply discount.","example":"SUMMER20"},"name":{"type":"string","example":"Summer sale"},"description":{"type":"string","example":"20% off everything through August"},"type":{"type":"string","description":"How `value` is interpreted — `percent`, `fixed`, `free_shipping`.","example":"percent"},"value":{"type":"number","example":20},"status":{"type":"string","description":"Only `active` discounts validate.","example":"active"},"autoApply":{"type":"boolean","description":"True for a discount with no code, applied automatically when the cart qualifies.","example":false},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number","description":"Total redemptions allowed across all customers.","example":500},"usageCount":{"type":"number","description":"Redemptions recorded so far.","example":37},"minCartValue":{"type":"number","example":50},"minCartItems":{"type":"number","example":2},"firstOrderOnly":{"type":"boolean","example":false},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the discount is limited to."},"excludedCustomerGroups":{"type":"array","items":{"type":"string"}},"requiredProducts":{"type":"array","items":{"type":"string"},"description":"SKUs that must be in the cart."}}}}}}}},"400":{"description":"Discount <identifier> not found — No discount matches that code or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Discount <identifier> not found","path":"/storefront/discounts/{identifier}/reverse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Discounts"],"description":"Undoes a recorded redemption for one order, decrementing the usage count. Use it when an order is cancelled or refunded so the code becomes available again.\n\n#### Signature\n\n```http\nPOST /storefront/discounts/{identifier}/reverse (identifier: string, body) -> The updated discount\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DISCOUNT_NOT_FOUND | Discount <identifier> not found | No discount matches that code or id. | List discounts with `GET /storefront/discounts`. Note this is a `400`, not a `404` — do not branch on the status alone. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/discounts/{identifier}/usage`","requestBody":{"description":"The order whose redemption is being reversed.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderId"],"properties":{"orderId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"}}},"example":{"orderId":"66f1a2b3c4d5e6f708192a3b"}}}}}},"/shipping/rates":{"post":{"operationId":"ShippingController_getShippingRates","summary":"Get shipping rates","description":"Quotes rates from the configured carriers for a set of parcels travelling between two addresses. This is the first step of the direct flow: quote, choose, then buy a label.\n\nPass the chosen rate object back to `POST /shipping/create` unchanged — carriers key their label purchase on the rate id, so a reconstructed object will not work.\n\n#### Signature\n\n```http\nPOST /shipping/rates (body) -> Available rates across configured carriers\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Rates expire. Buy the label promptly, or re-quote before purchasing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PROVIDER_NOT_CONFIGURED | Shipping integration not configured. Please configure EasyPost or another shipping provider. | Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\". | Configure a carrier integration — `GET /shipping/admin/providers` lists what is supported. Using the platform's shared carrier needs the org to accept its service agreement (a `402` otherwise). |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/create`\n- `POST /shipping/order/rates`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What is being shipped, and between where.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}},"description":"One entry per package."},"items":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines being shipped, when parcels should be derived from them."}}},"example":{"fromAddress":{"name":"Acme","street1":"4 Warehouse Way","city":"Newark","state":"NJ","zip":"07102","country":"US"},"toAddress":{"name":"Ada Lovelace","street1":"12 Ada Way","city":"Boston","state":"MA","zip":"02108","country":"US"},"parcels":[{"length":12,"width":9,"height":4,"weight":2.5}]}}}},"responses":{"201":{"description":"Available rates across configured carriers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A quoted rate from a carrier. Pass the whole object back when creating a label.","additionalProperties":true,"properties":{"id":{"type":"string","example":"rate_abc123"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"rate":{"type":"string","example":"8.45"},"currency":{"type":"string","example":"USD"},"delivery_days":{"type":"number","example":2}}}}}}},"400":{"description":"Shipping integration not configured. Please configure EasyPost or another shipping provider. — Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\".","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Shipping integration not configured. Please configure EasyPost or another shipping provider.","path":"/shipping/rates","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/create":{"post":{"operationId":"ShippingController_createShipping","summary":"Create a shipping label","description":"Buys the label for a chosen rate and creates the shipping record. **This spends money** — the carrier charges for the label at this point.\n\nNot idempotent: a retried request buys a second label. If a response is lost, look the shipment up with `GET /shipping/list` before trying again.\n\n#### Signature\n\n```http\nPOST /shipping/create (body) -> The shipping record, including the label and tracking number\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Buys a real label and incurs a real charge. Treat it as non-repeatable.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PROVIDER_NOT_CONFIGURED | Shipping integration not configured. Please configure EasyPost or another shipping provider. | Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\". | Configure a carrier integration — `GET /shipping/admin/providers` lists what is supported. Using the platform's shared carrier needs the org to accept its service agreement (a `402` otherwise). |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/rates`\n- `POST /shipping/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The chosen rate and the shipment details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rate":{"type":"object","description":"The rate object returned by `POST /shipping/rates`, passed back unchanged.","additionalProperties":true,"properties":{"id":{"type":"string","example":"rate_abc123"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"rate":{"type":"string","example":"8.45"},"currency":{"type":"string","example":"USD"},"delivery_days":{"type":"number","example":2}}},"orderNumber":{"type":"string","description":"Order to associate the shipment with.","example":"A7K2M9QX4"},"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}}}}},"example":{"orderNumber":"A7K2M9QX4","rate":{"id":"rate_abc123","carrier":"USPS","service":"Priority","rate":"8.45"}}}}},"responses":{"201":{"description":"The shipping record, including the label and tracking number","content":{"application/json":{"schema":{"type":"object","description":"A shipping record (`sf_shipping`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"trackingUrl":{"type":"string"},"status":{"type":"string","description":"Carrier status as last seen.","example":"in_transit"},"labelUrl":{"type":"string","description":"Where the purchased label can be downloaded."},"cost":{"type":"string","example":"8.45"},"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}}},"products":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines included in this shipment."}}}}}}}},"400":{"description":"Shipping integration not configured. Please configure EasyPost or another shipping provider. — Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\".","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Shipping integration not configured. Please configure EasyPost or another shipping provider.","path":"/shipping/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/refresh-tracking":{"post":{"operationId":"ShippingController_refreshTracking","summary":"Refresh tracking and apply any change","description":"Asks the carrier where a parcel is right now, then applies **the same rules as the webhook**: the order is updated and the customer emailed only when the carrier status differs from the one already processed.\n\nThat makes it safe to click repeatedly — an unchanged status costs one carrier lookup and does nothing else. No duplicate emails, no redundant writes.\n\nPass `orderNumber` to check every shipment on an order, or `trackingNumber` for one. Set `apply: false` to preview without writing anything, or `notify: false` to update the order without emailing the customer.\n\n#### Signature\n\n```http\nPOST /shipping/refresh-tracking (body) -> What changed, per shipment\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Idempotent by design — repeated calls with an unchanged carrier status have no side effects.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CARRIER_ERROR | The carrier rejected the request. | The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials. | The carrier message is passed through. Verify the address first with `POST /shipping/verify-address`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/tracking-webhook`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"trackingNumber","required":true,"in":"path","description":"Tracking number","schema":{}}],"requestBody":{"description":"What to check, and what to do about it.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"orderNumber":{"type":"string","description":"Check every shipment on this order.","example":"A7K2M9QX4"},"trackingNumber":{"type":"string","description":"Check a single tracking number.","example":"9400111899223197428490"},"apply":{"type":"boolean","default":true,"description":"`false` previews the outcome and writes nothing.","example":true},"notify":{"type":"boolean","default":true,"description":"`false` updates the order without emailing the customer.","example":true}}},"examples":{"wholeOrder":{"summary":"Refresh every shipment on an order","value":{"orderNumber":"A7K2M9QX4"}},"preview":{"summary":"Preview without writing","value":{"orderNumber":"A7K2M9QX4","apply":false}},"silent":{"summary":"Update without emailing the customer","value":{"trackingNumber":"9400111899223197428490","notify":false}}}}}},"responses":{"201":{"description":"What changed, per shipment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The carrier rejected the request. — The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The carrier rejected the request.","path":"/shipping/refresh-tracking","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/tracking-webhook":{"get":{"operationId":"ShippingController_getTrackingWebhookSetup","summary":"Read saved automatic tracking setup without contacting the provider","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Shipping"]},"post":{"operationId":"ShippingController_ensureTrackingWebhook","summary":"Register the tracking webhook","description":"Points the carrier integration at this org's webhook so carrier scans arrive automatically and shipping status updates without anyone polling.\n\n**Idempotent** — registrations are matched on URL, so calling it repeatedly is safe.\n\nWhen the provider is not configured it returns `{ ready: false, url }` rather than throwing, so setup flows can check readiness without handling an error.\n\n#### Signature\n\n```http\nPOST /shipping/tracking-webhook () -> Whether the webhook is registered, and at what URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Never throws for a missing provider — check `ready` in the response.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/refresh-tracking`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Whether the webhook is registered, and at what URL","content":{"application/json":{"schema":{"type":"object","properties":{"ready":{"type":"boolean","example":true},"url":{"type":"string","example":"https://api.appmint.io/shipping/webhook/acme-retail"}}},"example":{"ready":true,"url":"https://api.appmint.io/shipping/webhook/acme-retail"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/track/{trackingNumber}":{"get":{"operationId":"ShippingController_trackShipment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"trackingNumber","required":true,"in":"path","schema":{"type":"string"},"description":"Carrier tracking number.","example":"9400111899223197428490"}],"responses":{"200":{"description":"Tracking information from the carrier","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The carrier rejected the request. — The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The carrier rejected the request.","path":"/shipping/track/{trackingNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Shipping record not found — No shipping record matches the id, tracking number or order number given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping record not found","path":"/shipping/track/{trackingNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"],"summary":"Track a shipment","description":"Returns the carrier's current tracking information for a tracking number. A read-only lookup — it does not update the order or notify anyone.\n\nUse `POST /shipping/refresh-tracking` when you want a status change to actually propagate.\n\n#### Signature\n\n```http\nGET /shipping/track/{trackingNumber} (trackingNumber: string) -> Tracking information from the carrier\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIPPING_NOT_FOUND | Shipping record not found | No shipping record matches the id, tracking number or order number given. | List records with `GET /shipping/list`. |\n| `400` | CARRIER_ERROR | The carrier rejected the request. | The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials. | The carrier message is passed through. Verify the address first with `POST /shipping/verify-address`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/refresh-tracking`"}},"/shipping/google-keys":{"get":{"operationId":"ShippingController_getGoogleKeys","summary":"Get the Google Maps key for this org","description":"Returns the Maps API key a client should use, and says where it came from.\n\nWhen the org has configured its own Google provider with a `mapsApiKey`, that key is returned with `source: \"org\"` and `billable: false` — they pay Google directly and nothing is metered here.\n\nOtherwise the shared platform key is returned with `source: \"shared\"` and `billable: true`, and the org's agreement and balance are checked **before** the key is handed over. An org without an agreement or in arrears does not get a key.\n\n#### Signature\n\n```http\nGET /shipping/google-keys () -> The key and its billing provenance\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A `billable: true` response means the org is being charged for Maps usage — surface that in any UI that spends it heavily.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/address-autocomplete`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"required":true,"description":"Autocomplete request","content":{"application/json":{"schema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"User input text (minimum 3 characters)"},"sessiontoken":{"type":"string","description":"Session token for billing optimization (optional)"},"components":{"type":"string","description":"Country filter e.g. \"country:us|country:ca\" (default: US & CA)"}}}}}},"responses":{"200":{"description":"The key and its billing provenance","content":{"application/json":{"schema":{"type":"object","properties":{"mapsApiKey":{"type":"string","description":"Key to use client-side."},"source":{"type":"string","enum":["org","shared"],"description":"`org` means the org pays Google directly.","example":"shared"},"billable":{"type":"boolean","description":"Whether usage is metered and charged by the platform.","example":true}}},"example":{"mapsApiKey":"AIza...","source":"shared","billable":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/address-autocomplete":{"post":{"operationId":"ShippingController_getAddressAutocomplete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Address predictions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"],"summary":"Autocomplete an address","description":"Returns address predictions as the customer types, backed by Google Places. Defaults to US and Canada; pass `components` to widen or narrow that.\n\nSend a `sessiontoken` and keep it constant across the keystrokes of one lookup, then reuse it for the matching `place-details` call — Google bills an autocomplete session as a unit, and omitting the token makes every keystroke a separate billable request.\n\n#### Signature\n\n```http\nPOST /shipping/address-autocomplete (body) -> Address predictions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- May be billed against the shared platform Google key — see `GET /shipping/google-keys`.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /shipping/place-details/{placeId}`\n- `GET /shipping/google-keys`","requestBody":{"description":"What the customer has typed so far.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The typed text. At least 3 characters.","example":"12 Ada W"},"sessiontoken":{"type":"string","description":"Groups the keystrokes of one lookup into a single billable session. Reuse it for `place-details`.","example":"b1f2c3d4-e5f6"},"components":{"type":"string","default":"country:us|country:ca","description":"Country filter.","example":"country:us|country:ca"}}},"example":{"input":"12 Ada W","sessiontoken":"b1f2c3d4-e5f6","components":"country:us|country:ca"}}}}}},"/shipping/place-details/{placeId}":{"get":{"operationId":"ShippingController_getPlaceDetails","summary":"Get address details for a place","description":"Expands a place id from autocomplete into a full structured address ready to use as a shipping address.\n\n#### Signature\n\n```http\nGET /shipping/place-details/{placeId} (placeId: string) -> The structured address\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/address-autocomplete`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"placeId","required":true,"in":"path","description":"Google Place ID from an autocomplete prediction.","schema":{"type":"string"},"example":"ChIJd8BlQ2BZwokRAFUEcm_qrcA"}],"responses":{"200":{"description":"The structured address","content":{"application/json":{"schema":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/verify-address":{"post":{"operationId":"ShippingController_verifyShippingAddress","summary":"Verify a shipping address","description":"Validates and normalises an address with the carrier, correcting formatting and flagging anything undeliverable. Worth calling before buying a label — a bad address is the most common cause of a rejected shipment.\n\n#### Signature\n\n```http\nPOST /shipping/verify-address (body) -> The verified and normalised address\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CARRIER_ERROR | The carrier rejected the request. | The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials. | The carrier message is passed through. Verify the address first with `POST /shipping/verify-address`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/address-autocomplete`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The address to verify.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"example":{"name":"Ada Lovelace","street1":"12 Ada Way","city":"Boston","state":"MA","zip":"02108","country":"US"}}}},"responses":{"201":{"description":"The verified and normalised address","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The carrier rejected the request. — The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The carrier rejected the request.","path":"/shipping/verify-address","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/methods":{"get":{"operationId":"ShippingController_getShippingMethods","summary":"Get shipping methods","description":"The shipping methods and carriers available to this org. Use it to build a delivery-option selector rather than hard-coding carriers.\n\n#### Signature\n\n```http\nGET /shipping/methods () -> Available methods and carriers\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /shipping/admin/configs`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Available methods and carriers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/list":{"get":{"operationId":"ShippingController_listShippings","summary":"List shipping records","description":"Lists shipments with optional filters and paging.\n\n#### Signature\n\n```http\nGET /shipping/list (orderNumber?: string, status?: string, author?: string, page?: integer, pageSize?: integer) -> A page of shipping records\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /shipping/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orderNumber","required":false,"in":"query","schema":{"type":"string"},"example":"A7K2M9QX4"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"in_transit"},{"name":"author","required":false,"in":"query","description":"Filter by the customer or operator on the record.","schema":{"type":"string"},"example":"ada@example.com"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of shipping records","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A shipping record (`sf_shipping`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"trackingUrl":{"type":"string"},"status":{"type":"string","description":"Carrier status as last seen.","example":"in_transit"},"labelUrl":{"type":"string","description":"Where the purchased label can be downloaded."},"cost":{"type":"string","example":"8.45"},"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}}},"products":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines included in this shipment."}}}}}},"total":{"type":"integer","example":32}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/cancel":{"post":{"operationId":"ShippingController_cancelShipping","summary":"Cancel a shipment","description":"Cancels a shipment and requests a refund for the label from the carrier. Identify it by shipping id, tracking number or order number.\n\nCarriers only refund labels that have not entered their network, and refunds are typically processed on their own schedule rather than immediately.\n\n#### Signature\n\n```http\nPOST /shipping/cancel (body) -> The cancellation result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- A successful cancellation is a refund *request*. Whether the carrier honours it is their decision.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIPPING_NOT_FOUND | Shipping record not found | No shipping record matches the id, tracking number or order number given. | List records with `GET /shipping/list`. |\n| `400` | CARRIER_ERROR | The carrier rejected the request. | The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials. | The carrier message is passed through. Verify the address first with `POST /shipping/verify-address`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which shipment to cancel. Send at least one identifier.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shippingId":{"type":"string","description":"Shipping record id."},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"orderNumber":{"type":"string","example":"A7K2M9QX4"}}},"example":{"shippingId":"66f1a2b3c4d5e6f708192a3b"}}}},"responses":{"201":{"description":"The cancellation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The carrier rejected the request. — The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The carrier rejected the request.","path":"/shipping/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Shipping record not found — No shipping record matches the id, tracking number or order number given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping record not found","path":"/shipping/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/update-address":{"post":{"operationId":"ShippingController_updateShippingAddress","summary":"Update a delivery address","description":"Changes the delivery address on a shipment. **Only possible before the label is purchased** — once a carrier has the shipment, the address is fixed and the shipment must be cancelled and recreated.\n\n#### Signature\n\n```http\nPOST /shipping/update-address (body) -> The updated shipping record\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Cancel and recreate the shipment if the label has already been bought.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIPPING_NOT_FOUND | Shipping record not found | No shipping record matches the id, tracking number or order number given. | List records with `GET /shipping/list`. |\n| `400` | CARRIER_ERROR | The carrier rejected the request. | The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials. | The carrier message is passed through. Verify the address first with `POST /shipping/verify-address`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The shipment and its new address.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["shippingId","address"],"properties":{"shippingId":{"type":"string","example":"66f1a2b3c4d5e6f708192a3b"},"address":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}}}},"example":{"shippingId":"66f1a2b3c4d5e6f708192a3b","address":{"name":"Ada Lovelace","street1":"12 Ada Way","city":"Boston","state":"MA","zip":"02108","country":"US"}}}}},"responses":{"201":{"description":"The updated shipping record","content":{"application/json":{"schema":{"type":"object","description":"A shipping record (`sf_shipping`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"trackingUrl":{"type":"string"},"status":{"type":"string","description":"Carrier status as last seen.","example":"in_transit"},"labelUrl":{"type":"string","description":"Where the purchased label can be downloaded."},"cost":{"type":"string","example":"8.45"},"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}}},"products":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines included in this shipment."}}}}}}}},"400":{"description":"The carrier rejected the request. — The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The carrier rejected the request.","path":"/shipping/update-address","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Shipping record not found — No shipping record matches the id, tracking number or order number given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping record not found","path":"/shipping/update-address","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/estimated-delivery":{"post":{"operationId":"ShippingController_getEstimatedDelivery","summary":"Get estimated delivery times","description":"Returns estimated transit times per carrier and service for a route, without quoting prices or creating anything. Use it to show \"arrives Tuesday\" on a delivery-option selector.\n\nNarrow the result with `carrier` or `service` when you only care about one option.\n\n#### Signature\n\n```http\nPOST /shipping/estimated-delivery (body) -> Transit-time estimates per carrier and service\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Estimates are the carrier's, not a guarantee, and exclude your own handling time before dispatch.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PROVIDER_NOT_CONFIGURED | Shipping integration not configured. Please configure EasyPost or another shipping provider. | Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\". | Configure a carrier integration — `GET /shipping/admin/providers` lists what is supported. Using the platform's shared carrier needs the org to accept its service agreement (a `402` otherwise). |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/rates`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The route and parcels to estimate for.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["fromAddress","toAddress","parcels"],"properties":{"fromAddress":{"type":"object","description":"Origin address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"Destination address.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}},"description":"Package dimensions and weight."},"carrier":{"type":"string","description":"Restrict to one carrier.","example":"USPS"},"service":{"type":"string","description":"Restrict to one service level.","example":"Priority"}}},"example":{"fromAddress":{"name":"Acme","street1":"4 Warehouse Way","city":"Newark","state":"NJ","zip":"07102","country":"US"},"toAddress":{"name":"Ada Lovelace","street1":"12 Ada Way","city":"Boston","state":"MA","zip":"02108","country":"US"},"parcels":[{"length":12,"width":9,"height":4,"weight":2.5}]}}}},"responses":{"201":{"description":"Transit-time estimates per carrier and service","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"deliveryDays":{"type":"number","description":"Estimated transit days.","example":2},"estimatedDeliveryDate":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Shipping integration not configured. Please configure EasyPost or another shipping provider. — Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\".","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Shipping integration not configured. Please configure EasyPost or another shipping provider.","path":"/shipping/estimated-delivery","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/label/{shippingId}/{format}":{"get":{"operationId":"ShippingController_getShippingLabelById","summary":"Get a shipping label by id","description":"The URL form of the label retrieval, convenient for linking to directly from an operator UI.\n\n#### Signature\n\n```http\nGET /shipping/label/{shippingId}/{format} (shippingId: string, format: string) -> The label\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIPPING_NOT_FOUND | Shipping record not found | No shipping record matches the id, tracking number or order number given. | List records with `GET /shipping/list`. |\n| `400` | CARRIER_ERROR | The carrier rejected the request. | The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials. | The carrier message is passed through. Verify the address first with `POST /shipping/verify-address`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/label`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"shippingId","required":true,"in":"path","description":"Shipping record id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"format","required":false,"in":"query","description":"Label format","schema":{"enum":["PDF","PNG","ZPL","EPL2"],"type":"string"}},{"name":"format","in":"path","required":true,"description":"Label format — one of `PDF`, `PNG`, `ZPL`, `EPL2`. Defaults to PDF.","schema":{"type":"string"},"example":"PDF"}],"responses":{"200":{"description":"The label","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The carrier rejected the request. — The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The carrier rejected the request.","path":"/shipping/label/{shippingId}/{format}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Shipping record not found — No shipping record matches the id, tracking number or order number given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping record not found","path":"/shipping/label/{shippingId}/{format}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/label":{"post":{"operationId":"ShippingController_getShippingLabel","summary":"Get a shipping label","description":"Retrieves the label for an existing shipment in a chosen format. Identify it by shipping record id, provider shipment id, or tracking number.\n\n#### Signature\n\n```http\nPOST /shipping/label (body) -> The label\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIPPING_NOT_FOUND | Shipping record not found | No shipping record matches the id, tracking number or order number given. | List records with `GET /shipping/list`. |\n| `400` | CARRIER_ERROR | The carrier rejected the request. | The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials. | The carrier message is passed through. Verify the address first with `POST /shipping/verify-address`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /shipping/label/{shippingId}/{format}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which shipment, and in what format.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shippingId":{"type":"string","description":"Shipping record id."},"shipmentId":{"type":"string","description":"Provider shipment id."},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"format":{"type":"string","enum":["PDF","PNG","ZPL","EPL2"],"default":"PDF","description":"`ZPL` and `EPL2` are for thermal label printers.","example":"PDF"}}},"examples":{"pdf":{"summary":"PDF for a normal printer","value":{"shippingId":"66f1a2b3c4d5e6f708192a3b","format":"PDF"}},"thermal":{"summary":"ZPL for a thermal printer","value":{"trackingNumber":"9400111899223197428490","format":"ZPL"}}}}}},"responses":{"201":{"description":"The label","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The carrier rejected the request. — The carrier API returned an error — an undeliverable address, a parcel outside its limits, or missing credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The carrier rejected the request.","path":"/shipping/label","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Shipping record not found — No shipping record matches the id, tracking number or order number given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping record not found","path":"/shipping/label","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/{id}":{"get":{"operationId":"ShippingController_getShipping","summary":"Get a shipping record","description":"Fetches one shipment by record id **or** order number.\n\nThis route is declared last among the single-segment GETs, so `/shipping/list` and `/shipping/methods` resolve as their own endpoints rather than being read as ids.\n\n#### Signature\n\n```http\nGET /shipping/{id} (id: string) -> The shipping record\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIPPING_NOT_FOUND | Shipping record not found | No shipping record matches the id, tracking number or order number given. | List records with `GET /shipping/list`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /shipping/order/shipments/{orderNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Shipping record id or order number.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"The shipping record","content":{"application/json":{"schema":{"type":"object","description":"A shipping record (`sf_shipping`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"trackingUrl":{"type":"string"},"status":{"type":"string","description":"Carrier status as last seen.","example":"in_transit"},"labelUrl":{"type":"string","description":"Where the purchased label can be downloaded."},"cost":{"type":"string","example":"8.45"},"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}}},"products":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines included in this shipment."}}}}}}}},"404":{"description":"Shipping record not found — No shipping record matches the id, tracking number or order number given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping record not found","path":"/shipping/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/locations/available":{"get":{"operationId":"ShippingController_getAvailableLocations","summary":"Get available ship-from locations","description":"Locations that can be used as an origin address. Where an org has exactly one, the order endpoints select it automatically and `fromLocationId` can be omitted.\n\n#### Signature\n\n```http\nGET /shipping/locations/available () -> Locations usable as a shipping origin\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/order/rates`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Locations usable as a shipping origin","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/order/shipments/{orderNumber}":{"get":{"operationId":"ShippingController_getShipmentsForOrder","summary":"Get shipments for an order","description":"Every shipment raised against an order. An order shipped in parts has several, one per parcel or per fulfilment run.\n\n#### Signature\n\n```http\nGET /shipping/order/shipments/{orderNumber} (orderNumber: string) -> The order's shipments\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /shipping/order/status/{orderNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","description":"Public order number.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"The order's shipments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A shipping record (`sf_shipping`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"trackingUrl":{"type":"string"},"status":{"type":"string","description":"Carrier status as last seen.","example":"in_transit"},"labelUrl":{"type":"string","description":"Where the purchased label can be downloaded."},"cost":{"type":"string","example":"8.45"},"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}}},"products":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines included in this shipment."}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/order/status/{orderNumber}":{"get":{"operationId":"ShippingController_getOrderShippingStatus","summary":"Get shipping status for an order","description":"The fulfilment picture for an order: which lines have shipped and which are still outstanding.\n\nThis is the read that makes partial shipments manageable — it answers \"what is left to send\" without reconciling shipments against order lines yourself.\n\n#### Signature\n\n```http\nGET /shipping/order/status/{orderNumber} (orderNumber: string) -> Shipped and pending lines for the order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/order/rates`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orderNumber","required":true,"in":"path","description":"Public order number.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"Shipped and pending lines for the order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"shipped":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines already dispatched."},"pending":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines still to ship."}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/order/rates":{"post":{"operationId":"ShippingController_getShippingRatesForOrder","summary":"Get shipping rates for an order","description":"Quotes rates for an order, pulling the destination and the items from the order itself.\n\nFor a partial shipment, name the lines in `productSkus`; omit it to quote for everything outstanding. `fromLocationId` can be omitted when the org has only one location.\n\n#### Signature\n\n```http\nPOST /shipping/order/rates (body) -> Available rates for the order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PROVIDER_NOT_CONFIGURED | Shipping integration not configured. Please configure EasyPost or another shipping provider. | Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\". | Configure a carrier integration — `GET /shipping/admin/providers` lists what is supported. Using the platform's shared carrier needs the org to accept its service agreement (a `402` otherwise). |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/order/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The order, and optionally which lines to ship.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderNumber"],"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"productSkus":{"type":"array","items":{"type":"string"},"description":"Lines to ship. Omit for everything.","example":["DRK-COLA-330"]},"fromLocationId":{"type":"string","description":"Origin. Auto-selected when the org has one location.","example":"loc_warehouse"}}},"examples":{"whole":{"summary":"Quote for the whole order","value":{"orderNumber":"A7K2M9QX4"}},"partial":{"summary":"Quote for two lines only","value":{"orderNumber":"A7K2M9QX4","productSkus":["DRK-COLA-330","DRK-COLA-500"],"fromLocationId":"loc_warehouse"}}}}}},"responses":{"201":{"description":"Available rates for the order","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A quoted rate from a carrier. Pass the whole object back when creating a label.","additionalProperties":true,"properties":{"id":{"type":"string","example":"rate_abc123"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"rate":{"type":"string","example":"8.45"},"currency":{"type":"string","example":"USD"},"delivery_days":{"type":"number","example":2}}}}}}},"400":{"description":"Shipping integration not configured. Please configure EasyPost or another shipping provider. — Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\".","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Shipping integration not configured. Please configure EasyPost or another shipping provider.","path":"/shipping/order/rates","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/order/ship":{"post":{"operationId":"ShippingController_shipOrder","summary":"Create shipment for order","description":"Create a shipment for an order. Supports partial shipments by specifying specific products.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization ID","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Shipment creation request","content":{"application/json":{"schema":{"type":"object","required":["orderNumber","rate"],"properties":{"orderNumber":{"type":"string","description":"Order number"},"productSkus":{"type":"array","items":{"type":"string"},"description":"Specific product SKUs to ship (optional, ships all if not specified)"},"rate":{"type":"object","description":"Selected rate from getShippingRatesForOrder"},"fromLocationId":{"type":"string","description":"Ship-from location ID (optional, auto-selects if only one location)"}}}}}},"responses":{"200":{"description":"Returns created shipment with label"}},"tags":["Shipping"]}},"/shipping/order/create":{"post":{"operationId":"ShippingController_createShippingForOrder","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created shipment with its label","content":{"application/json":{"schema":{"type":"object","description":"A shipping record (`sf_shipping`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"trackingNumber":{"type":"string","example":"9400111899223197428490"},"trackingUrl":{"type":"string"},"status":{"type":"string","description":"Carrier status as last seen.","example":"in_transit"},"labelUrl":{"type":"string","description":"Where the purchased label can be downloaded."},"cost":{"type":"string","example":"8.45"},"fromAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"parcels":{"type":"array","items":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}}},"products":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lines included in this shipment."}}}}}}}},"400":{"description":"Shipping integration not configured. Please configure EasyPost or another shipping provider. — Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\".","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Shipping integration not configured. Please configure EasyPost or another shipping provider.","path":"/shipping/order/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"],"summary":"Create a shipment for an order","description":"Buys a label for an order and records the shipment against it. **This spends money.**\n\nSupports partial shipments through `productSkus` — ship what is in stock now and the rest later, and the order accumulates one shipment per dispatch.\n\nPass the `rate` object from `POST /shipping/order/rates` unchanged.\n\n#### Signature\n\n```http\nPOST /shipping/order/create (body) -> The created shipment with its label\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Buys a real label. Not idempotent — check `GET /shipping/order/shipments/{orderNumber}` before retrying.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PROVIDER_NOT_CONFIGURED | Shipping integration not configured. Please configure EasyPost or another shipping provider. | Neither the org nor the shared org has an active config whose `useCases` include `Shipping`. When the shared org is reached but has none either, the response is instead a `404` \"Integration with useCase Shipping not found in org <shared org>\". | Configure a carrier integration — `GET /shipping/admin/providers` lists what is supported. Using the platform's shared carrier needs the org to accept its service agreement (a `402` otherwise). |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/order/rates`\n- `POST /shipping/order/manual`","requestBody":{"description":"The order, the chosen rate, and optionally which lines.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderNumber","rate"],"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"rate":{"type":"object","description":"The rate object from the order rates call, unchanged.","additionalProperties":true,"properties":{"id":{"type":"string","example":"rate_abc123"},"carrier":{"type":"string","example":"USPS"},"service":{"type":"string","example":"Priority"},"rate":{"type":"string","example":"8.45"},"currency":{"type":"string","example":"USD"},"delivery_days":{"type":"number","example":2}}},"productSkus":{"type":"array","items":{"type":"string"},"description":"Lines in this shipment. Omit for everything outstanding.","example":["DRK-COLA-330"]},"fromLocationId":{"type":"string","example":"loc_warehouse"}}},"example":{"orderNumber":"A7K2M9QX4","rate":{"id":"rate_abc123","carrier":"USPS","service":"Priority","rate":"8.45"},"productSkus":["DRK-COLA-330"]}}}}}},"/shipping/order/manual":{"post":{"operationId":"ShippingController_addManualShipping","summary":"Add manual shipping info to an order","description":"Records shipping details for a parcel sent **outside** the carrier integration — a label bought at a post office counter, a courier booked directly, a local delivery driver. No carrier account is involved and no label is purchased.\n\nA tracking URL is generated automatically for carriers the platform recognises; pass `trackingUrl` for anything else.\n\nThe customer is emailed unless you pass `sendNotification: false`.\n\n#### Signature\n\n```http\nPOST /shipping/order/manual (body) -> The updated order with its shipping info\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- No carrier integration is needed, so this works even when no provider is configured.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/order/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The order and the shipping details to record.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderNumber","shippingInfo"],"properties":{"orderNumber":{"type":"string","example":"A7K2M9QX4"},"shippingInfo":{"type":"object","properties":{"carrier":{"type":"string","description":"Carrier name, or a description like `Local Delivery`.","example":"FedEx"},"tracker":{"type":"string","description":"Tracking number.","example":"794644790134"},"trackingUrl":{"type":"string","description":"Auto-generated for known carriers when omitted.","example":"https://www.fedex.com/fedextrack/?trknbr=794644790134"},"rate":{"type":"string","description":"Service name.","example":"Ground"},"cost":{"type":"string","example":"11.20"},"parcel":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}},"products":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"name":{"type":"string"},"quantity":{"type":"number"}}},"description":"Lines in this shipment."}}},"sendNotification":{"type":"boolean","default":true,"description":"`false` records the shipment without emailing the customer.","example":true}}},"examples":{"courier":{"summary":"Label bought at the counter","value":{"orderNumber":"A7K2M9QX4","shippingInfo":{"carrier":"FedEx","tracker":"794644790134","rate":"Ground","cost":"11.20"}}},"localDelivery":{"summary":"Local delivery, no tracking","value":{"orderNumber":"A7K2M9QX4","shippingInfo":{"carrier":"Local Delivery","rate":"Same day"},"sendNotification":true}}}}}},"responses":{"201":{"description":"The updated order with its shipping info","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/calculate":{"post":{"operationId":"ShippingController_calculateCartShipping","summary":"Calculate cart shipping cost","description":"Computes shipping for a cart from the org's shipping configuration — flat, weight-banded, zone-based or live carrier rates, whichever the config specifies — including any free-shipping threshold.\n\n#### Signature\n\n```http\nPOST /shipping/calculate (body) -> The calculated shipping cost\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Pass `orderTotal` or a free-shipping threshold cannot be evaluated and the customer will be charged.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/product-cost`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The cart and destination.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Cart lines."},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"orderTotal":{"type":"number","description":"Used to evaluate the free-shipping threshold.","example":129.99}}},"example":{"items":[{"sku":"DRK-COLA-330","quantity":2}],"toAddress":{"city":"Boston","state":"MA","zip":"02108","country":"US"},"orderTotal":129.99}}}},"responses":{"201":{"description":"The calculated shipping cost","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/options":{"post":{"operationId":"ShippingController_getShippingOptions","summary":"Shipping options for a cart and destination","description":"Every way this cart can be shipped to this address, cheapest first, following the shipping rules: a configuration scoped to the destination (country / state / postcode) beats an unscoped one, specificity then priority break ties, and per-product rules come first. Carrier configurations return one option per service.\n\nWhen items need different configurations the cart is split: each option's `amount` is the whole order's shipping and `groups` says how every part ships. Products priced \"free\" or \"flat\" on the product itself are folded in (`breakdown.forced`).\n\n`served: false` means no configuration ships there — `problems` and `message` say why; the answer is honest, not a guessed rate. A free-shipping promotion (automatic, or via `discountCode`) is applied after pricing: it frees the cheapest option, or every option when the promotion is set to `any`, marking them `freeShipping` with a `freeReason`. How to present the list is the client's call.\n\n#### Signature\n\n```http\nPOST /shipping/options (body) -> { served, options[], currency, problems?, message? }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Nothing configured at all returns `served: false` with \"Shipping has not been set up yet — add a shipping configuration to quote rates\".\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/calculate`\n- `POST /shipping/product-cost`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items","toAddress"],"properties":{"items":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"sku":{"type":"string"},"quantity":{"type":"number"}}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"fromAddress":{"type":"object","description":"Origin. Optional — the configuration's origin or the org location otherwise.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"orderTotal":{"type":"number","description":"For free-over thresholds. Computed from the items when omitted."},"discountCode":{"type":"string","description":"Optional coupon; automatic promotions apply without one."}}},"example":{"items":[{"sku":"DRK-COLA-330","quantity":2}],"toAddress":{"street1":"1 Main St","city":"Boston","state":"MA","zip":"02108","country":"US"},"orderTotal":42}}}},"responses":{"201":{"description":"{ served, options[], currency, problems?, message? }","content":{"application/json":{"schema":{"type":"object","properties":{"served":{"type":"boolean"},"currency":{"type":"string","example":"USD"},"message":{"type":"string"},"problems":{"type":"array","items":{"type":"object","properties":{"config":{"type":"string"},"reason":{"type":"string"}}}},"options":{"type":"array","items":{"type":"object","properties":{"config":{"type":"string"},"label":{"type":"string","example":"Standard"},"method":{"type":"string","enum":["free","flat","weight","carrier","pickup"]},"provider":{"type":"string"},"carrier":{"type":"string"},"service":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"freeShipping":{"type":"boolean"},"freeReason":{"type":"string"},"etaDays":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"},"text":{"type":"string"}}},"breakdown":{"type":"object","properties":{"base":{"type":"number"},"handling":{"type":"number"},"markup":{"type":"number"},"forced":{"type":"number"}}},"dimensionsEstimated":{"type":"boolean"},"pickup":{"type":"object","properties":{"locations":{"type":"array","items":{"type":"string"}},"instructions":{"type":"string"},"readyInHours":{"type":"number"}}},"groups":{"type":"array","items":{"type":"object","properties":{"config":{"type":"string"},"label":{"type":"string"},"method":{"type":"string"},"amount":{"type":"number"},"items":{"type":"array","items":{"type":"string"}}}}}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/product-cost":{"post":{"operationId":"ShippingController_getProductShippingCost","summary":"Get shipping cost for one product","description":"Shipping cost for a single product, for display on a product page (\"+ $4.99 shipping\"). Falls back to a US destination and the org's own location when addresses are omitted, so it can be called before a customer has entered anything.\n\n#### Signature\n\n```http\nPOST /shipping/product-cost (body) -> The product's shipping cost\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The default destination makes the figure indicative only — recompute at checkout with the real address.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /shipping/calculate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The product and, optionally, where it is going.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"productId":{"type":"string"},"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","default":1,"example":1},"toAddress":{"type":"object","description":"Destination. Defaults to a US address when omitted.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}},"fromAddress":{"type":"object","description":"Origin. Defaults to the org location.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}}}},"example":{"sku":"DRK-COLA-330","quantity":1}}}},"responses":{"201":{"description":"The product's shipping cost","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping"]}},"/shipping/admin/providers":{"get":{"operationId":"ShippingController_getShippingProviderTypes","summary":"List supported shipping providers","description":"The carrier provider types the platform supports — EasyPost, Shippo, FedEx and so on — each with a description and setup help. This is a platform-level list and takes no org.\n\n#### Signature\n\n```http\nGET /shipping/admin/providers () -> Supported provider types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The only endpoint here that does not read the `orgid` header — the list is the same for every org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /shipping/admin/integrations`","parameters":[],"responses":{"200":{"description":"Supported provider types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/integrations":{"get":{"operationId":"ShippingController_getShippingIntegrations","summary":"Get configured shipping integrations","description":"The carrier integrations this org has configured, alongside the providers still available to add.\n\n#### Signature\n\n```http\nGET /shipping/admin/integrations () -> Configured integrations and available providers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /shipping/admin/providers`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Configured integrations and available providers","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/configs":{"get":{"operationId":"ShippingController_listShippingConfigs","summary":"List shipping configurations","description":"All shipping rate configurations for the org. One is the site default; the rest are selected per product or per config name.\n\n#### Signature\n\n```http\nGET /shipping/admin/configs () -> The org's shipping configurations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/configs`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The org's shipping configurations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]},"post":{"operationId":"ShippingController_createShippingConfig","summary":"Create a shipping configuration","description":"Creates a rate configuration. `method` picks the pricing model and decides which of the other blocks matter:\n\n- `free` — no charge.\n- `flat` — a fixed rate, optionally per item. Uses `flatRate`.\n- `weight` — banded by weight. Uses the weight tiers and `defaultRate`.\n- `zone` — banded by destination.\n- `carrier` — live rates from the configured carrier. Uses `carrier` and `origin`.\n\n`freeShipping` and `handling` apply on top of any method.\n\n#### Signature\n\n```http\nPOST /shipping/admin/configs (body) -> The created configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `name` is the identifier for every other config endpoint, so pick it carefully — there is no rename.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/configs/{name}`\n- `POST /shipping/admin/preview-rate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The configuration to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","method"],"properties":{"name":{"type":"string","description":"Unique slug. This is the identifier used by the other config endpoints.","example":"standard-flat"},"title":{"type":"string","example":"Standard shipping"},"method":{"type":"string","enum":["free","flat","weight","zone","carrier"],"example":"flat"},"isDefault":{"type":"boolean","description":"Make this the site default.","example":true},"currency":{"type":"string","default":"USD","example":"USD"},"flatRate":{"type":"object","properties":{"rate":{"type":"number","example":4.99},"perItem":{"type":"boolean","description":"Charge the rate per item instead of per order.","example":false}}},"weight":{"type":"object","description":"Weight-banded rates. Used when `method` is `weight`.","properties":{"weightUnit":{"type":"string","example":"lb"},"dimensionUnit":{"type":"string","example":"in"},"tiers":{"type":"array","items":{"type":"object","additionalProperties":true}},"defaultRate":{"type":"number","example":9.99}}},"freeShipping":{"type":"object","properties":{"enabled":{"type":"boolean","example":true},"threshold":{"type":"number","description":"Order total at which shipping becomes free.","example":75}}},"handling":{"type":"object","properties":{"feePerOrder":{"type":"number","example":1.5},"feePerItem":{"type":"number","example":0.25}}},"origin":{"type":"object","additionalProperties":true,"description":"Ship-from address for rate calculation."},"carrier":{"type":"object","additionalProperties":true,"description":"Carrier settings, used when `method` is `carrier`."},"restrictions":{"type":"object","additionalProperties":true,"description":"Where this configuration may be used."}}},"examples":{"flat":{"summary":"Flat rate with free shipping over $75","value":{"name":"standard-flat","title":"Standard shipping","method":"flat","isDefault":true,"currency":"USD","flatRate":{"rate":4.99,"perItem":false},"freeShipping":{"enabled":true,"threshold":75}}},"carrier":{"summary":"Live carrier rates","value":{"name":"live-rates","title":"Carrier rates","method":"carrier","currency":"USD","handling":{"feePerOrder":1.5}}}}}}},"responses":{"201":{"description":"The created configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/configs/{name}":{"get":{"operationId":"ShippingController_getShippingConfig","summary":"Get a shipping configuration","description":"Fetches one shipping configuration by name, including its rate rules, free-shipping threshold and handling fees.\n\n#### Signature\n\n```http\nGET /shipping/admin/configs/{name} (name: string) -> The shipping configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CONFIG_NOT_FOUND | Shipping configuration not found | No config in the org has that name. | List them with `GET /shipping/admin/configs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/configs/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Shipping config name (slug).","schema":{"type":"string"},"example":"standard-flat"}],"responses":{"200":{"description":"The shipping configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Shipping configuration not found — No config in the org has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping configuration not found","path":"/shipping/admin/configs/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]},"post":{"operationId":"ShippingController_updateShippingConfig","summary":"Update a shipping configuration","description":"Updates an existing configuration. Note this is a `POST`, not a `PUT` — the whole config admin surface uses `POST` for writes, including delete.\n\n#### Signature\n\n```http\nPOST /shipping/admin/configs/{name} (name: string, body) -> The updated configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Test a change with `POST /shipping/admin/preview-rate` before making it the default.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CONFIG_NOT_FOUND | Shipping configuration not found | No config has that name. | Check the name with `GET /shipping/admin/configs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/preview-rate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Shipping config name (slug).","schema":{"type":"string"},"example":"standard-flat"}],"requestBody":{"description":"The fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"flatRate":{"rate":5.99,"perItem":false},"freeShipping":{"enabled":true,"threshold":100}}}}},"responses":{"201":{"description":"The updated configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Shipping configuration not found — No config has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping configuration not found","path":"/shipping/admin/configs/{name}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/configs/delete/{name}":{"post":{"operationId":"ShippingController_deleteShippingConfig","summary":"Delete a shipping configuration","description":"Deletes a shipping configuration.\n\nNote the unusual shape: deletion is a `POST` to `/delete/{name}` rather than a `DELETE`. Products still pointing at the deleted config fall back to the site default.\n\n#### Signature\n\n```http\nPOST /shipping/admin/configs/delete/{name} (name: string) -> Confirmation of the delete\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Check which products reference the config before deleting — they silently fall back to the default.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CONFIG_NOT_FOUND | Shipping configuration not found | No config has that name. | Check the name first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/configs/set-default/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Shipping config name (slug).","schema":{"type":"string"},"example":"standard-flat"}],"responses":{"201":{"description":"Confirmation of the delete","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Shipping configuration not found — No config has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping configuration not found","path":"/shipping/admin/configs/delete/{name}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/configs/set-default/{name}":{"post":{"operationId":"ShippingController_setDefaultShippingConfig","summary":"Set the default shipping configuration","description":"Makes one configuration the site default — what applies to any product that does not name its own. Setting a new default clears the flag on the previous one.\n\n#### Signature\n\n```http\nPOST /shipping/admin/configs/set-default/{name} (name: string) -> Confirmation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CONFIG_NOT_FOUND | Shipping configuration not found | No config has that name. | Check the name first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /shipping/admin/configs`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Shipping config name (slug).","schema":{"type":"string"},"example":"standard-flat"}],"responses":{"201":{"description":"Confirmation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Shipping configuration not found — No config has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shipping configuration not found","path":"/shipping/admin/configs/set-default/{name}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/product/shipping":{"post":{"operationId":"ShippingController_updateProductShipping","summary":"Update shipping settings for a product","description":"Sets a product's shipping settings — its dimensions, weight, and which shipping config applies to it.\n\n#### Signature\n\n```http\nPOST /shipping/admin/product/shipping (body) -> Confirmation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Weight and dimensions matter for `weight`-method configs and for live carrier rates — a product without them will be quoted badly.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/products/shipping`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The product and its shipping settings.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"productId":{"type":"string","description":"Product id or SKU.","example":"DRK-COLA-330"},"shipping":{"type":"object","additionalProperties":true,"properties":{"config":{"type":"string","description":"Shipping config name to use for this product.","example":"standard-flat"},"weight":{"type":"number","example":0.8},"length":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"}}}}},"example":{"productId":"DRK-COLA-330","shipping":{"config":"standard-flat","weight":0.8,"length":3,"width":3,"height":5}}}}},"responses":{"201":{"description":"Confirmation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/products/shipping":{"post":{"operationId":"ShippingController_bulkUpdateProductShipping","summary":"Bulk update product shipping settings","description":"Applies the same shipping settings to many products at once — the practical way to move a whole category onto a new shipping config.\n\n#### Signature\n\n```http\nPOST /shipping/admin/products/shipping (body) -> Confirmation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The same settings go to every product — send dimensions only when they genuinely apply to all of them.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/product/shipping`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The products and the settings to apply to all of them.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["productIds","shipping"],"properties":{"productIds":{"type":"array","items":{"type":"string"},"description":"Product ids or SKUs.","example":["DRK-COLA-330","DRK-COLA-500"]},"shipping":{"type":"object","additionalProperties":true,"properties":{"config":{"type":"string","description":"Shipping config name.","example":"standard-flat"}}}}},"example":{"productIds":["DRK-COLA-330","DRK-COLA-500"],"shipping":{"config":"standard-flat"}}}}},"responses":{"201":{"description":"Confirmation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/preview-rate":{"post":{"operationId":"ShippingController_previewShippingRate","summary":"Preview a rate calculation","description":"Runs a shipping configuration against a test parcel and returns what it would charge, without creating any record or contacting a carrier for a real quote.\n\nThis is how a rate change gets verified before it reaches customers. Omit `configName` to test the default, and pass `productPrice` so any free-shipping threshold is exercised too.\n\n#### Signature\n\n```http\nPOST /shipping/admin/preview-rate (body) -> The rate the configuration would charge\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Writes nothing and buys nothing — safe to call as often as you like.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/configs/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The configuration and the test parcel.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"configName":{"type":"string","description":"Config to test. Omit for the default.","example":"standard-flat"},"productPrice":{"type":"number","description":"Order value, so the free-shipping threshold is evaluated.","example":80},"parcel":{"type":"object","description":"A physical package. Units follow the org's shipping configuration.","properties":{"length":{"type":"number","example":12},"width":{"type":"number","example":9},"height":{"type":"number","example":4},"weight":{"type":"number","example":2.5}}},"toAddress":{"type":"object","description":"A shipping address in the carrier's field naming.","properties":{"name":{"type":"string","example":"Ada Lovelace"},"company":{"type":"string"},"street1":{"type":"string","example":"12 Ada Way"},"street2":{"type":"string"},"city":{"type":"string","example":"Newark"},"state":{"type":"string","example":"NJ"},"zip":{"type":"string","example":"07102"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"phone":{"type":"string","example":"+15551234567"},"email":{"type":"string","example":"ada@example.com"}}}}},"examples":{"belowThreshold":{"summary":"Below the free-shipping threshold","value":{"configName":"standard-flat","productPrice":40,"parcel":{"length":12,"width":9,"height":4,"weight":2.5}}},"aboveThreshold":{"summary":"Above the threshold — expect zero","value":{"configName":"standard-flat","productPrice":120,"parcel":{"length":12,"width":9,"height":4,"weight":2.5}}}}}}},"responses":{"201":{"description":"The rate the configuration would charge","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/calculate-packing":{"post":{"operationId":"ShippingController_calculatePacking","summary":"Preview how items would be packed","description":"Shows how items would be distributed into boxes without creating a shipment — which box sizes get used and what goes in each.\n\nPass an `orderNumber` to pack the order's items, or supply `items` inline to test a hypothetical basket. Useful for checking box sizes are configured sensibly before a rate quote surprises you.\n\n#### Signature\n\n```http\nPOST /shipping/admin/calculate-packing (body) -> The packing breakdown, with box assignments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /shipping/admin/preview-rate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to pack. Send an order number or items, not both.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"orderNumber":{"type":"string","description":"Pull the items from this order.","example":"A7K2M9QX4"},"items":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string"},"sku":{"type":"string"},"quantity":{"type":"number"}}},"description":"Inline items, as an alternative to `orderNumber`."}}},"examples":{"fromOrder":{"summary":"Pack an order","value":{"orderNumber":"A7K2M9QX4"}},"inline":{"summary":"Pack a hypothetical basket","value":{"items":[{"sku":"DRK-COLA-330","quantity":24}]}}}}}},"responses":{"201":{"description":"The packing breakdown, with box assignments","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/shipping/admin/stats":{"get":{"operationId":"ShippingController_getShippingStats","summary":"Get shipping statistics","description":"Aggregate shipping figures for the org — volume, spend and carrier mix.\n\n#### Signature\n\n```http\nGET /shipping/admin/stats () -> Aggregate shipping statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /shipping/list`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Aggregate shipping statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Shipping · Admin"]}},"/storefront/pricing/calculate":{"post":{"operationId":"PricingController_calculatePrice","summary":"Calculate a product price","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The resolved price, with everything that contributed","content":{"application/json":{"schema":{"type":"object","properties":{"originalPrice":{"type":"number","description":"Base price before the pipeline ran.","example":12},"finalPrice":{"type":"number","description":"Unit price after every rule.","example":9.6},"unitPrice":{"type":"number","description":"Same as `finalPrice`. Kept for clients that read this name.","example":9.6},"totalPrice":{"type":"number","description":"`finalPrice × quantity`.","example":19.2},"discount":{"type":"number","description":"Absolute amount taken off the unit price.","example":2.4},"discountPercent":{"type":"number","example":20},"quantity":{"type":"number","example":2},"currency":{"type":"string","description":"From the product, defaulting to `USD`.","example":"USD"},"appliedDiscounts":{"type":"array","description":"Every rule that contributed, in the order it applied.","items":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"summer-sale"},"amount":{"type":"number","example":2.4}}}},"appliedRule":{"type":"object","additionalProperties":true,"description":"The winning price rule, when one won."}}},"example":{"originalPrice":12,"finalPrice":9.6,"unitPrice":9.6,"totalPrice":115.2,"discount":2.4,"discountPercent":20,"quantity":12,"currency":"USD","appliedDiscounts":[{"name":"bulk-12","amount":2.4}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The requested resource was not found — The `sku` does not resolve to a product in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"The requested resource was not found","path":"/storefront/pricing/calculate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Resolves what one product costs for the calling customer at a given quantity.\n\nPricing runs as an ordered pipeline, and the first stage that produces a price stops the search for a base:\n\n1. The product's base price, or the absolute price of a matched variation.\n2. Product-level **tiered** pricing, when the quantity qualifies.\n3. Product-level **group** pricing, when the customer is in a qualifying group.\n4. **Price lists**, used only as a fallback when none of the above applied.\n5. **Group benefits** — a further discount from the customer's group.\n6. **Auto-apply discounts**.\n\nPer-option surcharges are deliberately added *after* the pipeline, not folded into the base: tier and price-list prices are themselves base prices, so adding the surcharge first would let a tier overwrite it.\n\nThe customer is taken from the authenticated caller, not the body — an anonymous request gets anonymous pricing. Use `preview-as-customer` to price as someone else.\n\n#### Signature\n\n```http\nPOST /storefront/pricing/calculate (body) -> The resolved price, with everything that contributed\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The price depends on who is calling. Do not cache a result across customers.\n- `quantity` changes the *unit* price when a tier applies — it is not just a multiplier.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRODUCT_NOT_FOUND | The requested resource was not found | The `sku` does not resolve to a product in the org. | Check the SKU with `GET /storefront/products`. The server knows which one was missing but does not tell you: the underlying error is a plain `Error`, and the global filter rewrites its message to the generic text for anything that is not an `HttpException`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pricing/calculate-cart`\n- `POST /storefront/pricing/preview-as-customer`","requestBody":{"description":"The product and quantity to price.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku"],"properties":{"sku":{"type":"string","description":"Product SKU.","example":"DRK-COLA-330"},"quantity":{"type":"number","default":1,"description":"Quantity. Drives tier selection, so it changes the unit price.","example":12}}},"examples":{"single":{"summary":"One unit","value":{"sku":"DRK-COLA-330"}},"tierQuantity":{"summary":"A quantity that may hit a tier","value":{"sku":"DRK-COLA-330","quantity":12}}}}}}}},"/storefront/pricing/calculate-cart":{"post":{"operationId":"PricingController_calculateCartPrices","summary":"Calculate cart prices","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The priced cart with totals and applied discounts","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Prices a whole cart in one pass, including coupon application and any automatic discounts.\n\nThis is handled by the discount service rather than the pricing service, deliberately: cart-level discounting is a single source of truth, so a coupon and an auto-apply rule cannot both claim the same basket through different code paths.\n\nProducts and rentals are priced together but on their own terms — pass each in its own array.\n\n**Render `total` as given.** It already includes tax and shipping. A client that computes `subtotal + shipping - discount` drops tax entirely — and agrees with the server on tax-free destinations, which is exactly why that bug survives review.\n\n#### Signature\n\n```http\nPOST /storefront/pricing/calculate-cart (body) -> The priced cart with totals and applied discounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- An invalid coupon does not fail the request — inspect the response to find out whether it applied.\n- A product's `data.calculatedPrice` is an OBJECT — `{ finalPrice, originalPrice, discount, discountPercent, appliedDiscounts }` — not a number. Read `.finalPrice`; rendering the object yields `[object Object]`.\n- Checkbox-style options contribute the SUM of every checked option's price. Quantity multiplies the line and is not a delta.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/discounts/apply`\n- `POST /storefront/pricing/calculate`","requestBody":{"description":"The cart to price.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"productItems":{"type":"array","description":"Ordinary product lines.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","example":2},"options":{"type":"array","description":"Chosen configurable-product options. A price delta is counted ONLY when it arrives here, with a numeric `price`. Choices recorded in a freeform `attributes` object are displayed but never charged.","items":{"type":"object","properties":{"name":{"type":"string","example":"color"},"label":{"type":"string","example":"Colour"},"value":{"type":"string","example":"Oak"},"price":{"type":"number","example":12}}}}}}},"rentalItems":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Rental lines, priced on their own rules."},"couponCode":{"type":"string","description":"Promo code to validate and apply. An invalid code is reported in the response rather than thrown.","example":"SUMMER20"},"shippingAddress":{"type":"object","additionalProperties":true,"description":"Used where a discount or price depends on destination."}}},"examples":{"products":{"summary":"A product-only cart","value":{"productItems":[{"sku":"DRK-COLA-330","quantity":2}]}},"withCoupon":{"summary":"With a coupon","value":{"productItems":[{"sku":"DRK-COLA-330","quantity":2}],"couponCode":"SUMMER20"}}}}}}}},"/storefront/pricing/product/{sku}":{"get":{"operationId":"PricingController_getProductPricingInfo","summary":"Get pricing variations for a product","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"DRK-COLA-330"}],"responses":{"200":{"description":"The pricing variations available to this customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The requested resource was not found — The `sku` does not resolve to a product in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"The requested resource was not found","path":"/storefront/pricing/product/{sku}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Returns every price this product can take for the calling customer — the tiers, the group prices and the price-list entries that could apply — rather than resolving a single one.\n\nUse it to render a \"buy 12 for $9 each\" table, where the customer needs to see the ladder rather than just the price at their current quantity.\n\n#### Signature\n\n```http\nGET /storefront/pricing/product/{sku} (sku: string) -> The pricing variations available to this customer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRODUCT_NOT_FOUND | The requested resource was not found | The `sku` does not resolve to a product in the org. | Check the SKU with `GET /storefront/products`. The server knows which one was missing but does not tell you: the underlying error is a plain `Error`, and the global filter rewrites its message to the generic text for anything that is not an `HttpException`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pricing/calculate`"}},"/storefront/pricing/customer-price-lists":{"get":{"operationId":"PricingController_getCustomerPriceLists","summary":"Get the calling customer's price lists","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The customer's applicable price lists and groups","content":{"application/json":{"schema":{"type":"object","properties":{"priceLists":{"type":"array","items":{"type":"object","description":"A price list (`sf_price_list`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Unique name. Doubles as the identifier in the path and **cannot be changed** by an update.","example":"wholesale"},"title":{"type":"string","example":"Wholesale pricing"},"status":{"type":"string","description":"Only `active` lists are considered when resolving a customer's pricing.","example":"active"},"priority":{"type":"number","description":"Higher wins when several lists target the same customer. Lists are resolved in descending priority.","example":10},"currency":{"type":"string","example":"USD"},"prices":{"type":"array","description":"The prices this list sets.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","example":9},"minQuantity":{"type":"number","description":"Quantity at which this price starts applying.","example":12}}}},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups this list targets.","example":["wholesale"]},"customerEmails":{"type":"array","items":{"type":"string"},"description":"Individual customers this list targets."}}}}},"description":"Active, targeting this customer, highest priority first."},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups the customer belongs to.","example":["wholesale"]}}},"example":{"priceLists":[],"customerGroups":[]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Returns the active price lists that target the signed-in customer, ordered by descending priority, together with the groups they belong to.\n\nAn anonymous caller gets empty arrays rather than an error, so a storefront can call this before sign-in without special-casing.\n\n#### Signature\n\n```http\nGET /storefront/pricing/customer-price-lists () -> The customer's applicable price lists and groups\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Anonymous callers receive `{ priceLists: [], customerGroups: [] }` with a `200`.\n- Only lists with `status: \"active\"` are considered — a draft list never appears here.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/pricing/price-lists`"}},"/storefront/pricing/price-lists":{"get":{"operationId":"PricingController_listPriceLists","summary":"List price lists","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"All price lists","content":{"application/json":{"schema":{"type":"object","properties":{"priceLists":{"type":"array","items":{"type":"object","description":"A price list (`sf_price_list`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Unique name. Doubles as the identifier in the path and **cannot be changed** by an update.","example":"wholesale"},"title":{"type":"string","example":"Wholesale pricing"},"status":{"type":"string","description":"Only `active` lists are considered when resolving a customer's pricing.","example":"active"},"priority":{"type":"number","description":"Higher wins when several lists target the same customer. Lists are resolved in descending priority.","example":10},"currency":{"type":"string","example":"USD"},"prices":{"type":"array","description":"The prices this list sets.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","example":9},"minQuantity":{"type":"number","description":"Quantity at which this price starts applying.","example":12}}}},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups this list targets.","example":["wholesale"]},"customerEmails":{"type":"array","items":{"type":"string"},"description":"Individual customers this list targets."}}}}}},"total":{"type":"integer","example":3}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Returns every price list in the org, regardless of status, with a total. Unpaged.\n\n#### Signature\n\n```http\nGET /storefront/pricing/price-lists () -> All price lists\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Includes inactive and draft lists — filter on `data.status` if you only want the live ones.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pricing/price-lists`"},"post":{"operationId":"PricingController_createPriceList","summary":"Create a price list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created price list","content":{"application/json":{"schema":{"type":"object","description":"A price list (`sf_price_list`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Unique name. Doubles as the identifier in the path and **cannot be changed** by an update.","example":"wholesale"},"title":{"type":"string","example":"Wholesale pricing"},"status":{"type":"string","description":"Only `active` lists are considered when resolving a customer's pricing.","example":"active"},"priority":{"type":"number","description":"Higher wins when several lists target the same customer. Lists are resolved in descending priority.","example":10},"currency":{"type":"string","example":"USD"},"prices":{"type":"array","description":"The prices this list sets.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","example":9},"minQuantity":{"type":"number","description":"Quantity at which this price starts applying.","example":12}}}},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups this list targets.","example":["wholesale"]},"customerEmails":{"type":"array","items":{"type":"string"},"description":"Individual customers this list targets."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Creates a price list from the body as given.\n\n**Nothing is validated and nothing is de-duplicated.** There is no uniqueness check on `name`, so creating a list with an existing name succeeds and leaves two lists sharing it — after which `GET`, `PUT` and `DELETE` by that name all act on whichever the lookup returns first. Check the name is free before creating.\n\nThe record is authored as `system` rather than the calling user.\n\n#### Signature\n\n```http\nPOST /storefront/pricing/price-lists (body) -> The created price list\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Duplicate names are accepted. The name is the identifier used by every other endpoint here, so a duplicate makes those endpoints ambiguous.\n- Only `status: \"active\"` lists take part in pricing.\n- Records are authored as `system`, so the audit trail does not name the operator who created it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/pricing/price-lists/{name}`\n- `GET /storefront/pricing/price-lists`","requestBody":{"description":"The price list to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Unique name. Doubles as the identifier in the path and **cannot be changed** by an update.","example":"wholesale"},"title":{"type":"string","example":"Wholesale pricing"},"status":{"type":"string","description":"Only `active` lists are considered when resolving a customer's pricing.","example":"active"},"priority":{"type":"number","description":"Higher wins when several lists target the same customer. Lists are resolved in descending priority.","example":10},"currency":{"type":"string","example":"USD"},"prices":{"type":"array","description":"The prices this list sets.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","example":9},"minQuantity":{"type":"number","description":"Quantity at which this price starts applying.","example":12}}}},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups this list targets.","example":["wholesale"]},"customerEmails":{"type":"array","items":{"type":"string"},"description":"Individual customers this list targets."}}},"example":{"name":"wholesale","title":"Wholesale pricing","status":"active","priority":10,"currency":"USD","customerGroups":["wholesale"],"prices":[{"sku":"DRK-COLA-330","price":9,"minQuantity":12}]}}}}}},"/storefront/pricing/price-lists/{name}":{"get":{"operationId":"PricingController_getPriceList","summary":"Get a price list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Price list `name`, not its `sk`.","example":"wholesale"}],"responses":{"200":{"description":"The price list, or `null` when no list has that name","content":{"application/json":{"schema":{"type":"object","description":"A price list (`sf_price_list`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Unique name. Doubles as the identifier in the path and **cannot be changed** by an update.","example":"wholesale"},"title":{"type":"string","example":"Wholesale pricing"},"status":{"type":"string","description":"Only `active` lists are considered when resolving a customer's pricing.","example":"active"},"priority":{"type":"number","description":"Higher wins when several lists target the same customer. Lists are resolved in descending priority.","example":10},"currency":{"type":"string","example":"USD"},"prices":{"type":"array","description":"The prices this list sets.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","example":9},"minQuantity":{"type":"number","description":"Quantity at which this price starts applying.","example":12}}}},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups this list targets.","example":["wholesale"]},"customerEmails":{"type":"array","items":{"type":"string"},"description":"Individual customers this list targets."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Fetches one price list by name.\n\nA name that does not exist returns **`null` with a `200`**, not a `404` — this read does not throw. Check for a null body.\n\n#### Signature\n\n```http\nGET /storefront/pricing/price-lists/{name} (name: string) -> The price list, or `null` when no list has that name\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Unlike update and delete, a missing list is `null` + `200` here rather than a `404`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/pricing/price-lists/{name}`"},"put":{"operationId":"PricingController_updatePriceList","summary":"Update a price list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Price list `name`, not its `sk`.","example":"wholesale"}],"responses":{"200":{"description":"The updated price list","content":{"application/json":{"schema":{"type":"object","description":"A price list (`sf_price_list`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Unique name. Doubles as the identifier in the path and **cannot be changed** by an update.","example":"wholesale"},"title":{"type":"string","example":"Wholesale pricing"},"status":{"type":"string","description":"Only `active` lists are considered when resolving a customer's pricing.","example":"active"},"priority":{"type":"number","description":"Higher wins when several lists target the same customer. Lists are resolved in descending priority.","example":10},"currency":{"type":"string","example":"USD"},"prices":{"type":"array","description":"The prices this list sets.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","example":9},"minQuantity":{"type":"number","description":"Quantity at which this price starts applying.","example":12}}}},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups this list targets.","example":["wholesale"]},"customerEmails":{"type":"array","items":{"type":"string"},"description":"Individual customers this list targets."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The requested resource was not found — No price list in the org has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"The requested resource was not found","path":"/storefront/pricing/price-lists/{name}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Merges the body into the existing list — fields you omit keep their current values.\n\n**`name` cannot be changed.** It is re-applied from the path after the merge, so sending a different `name` in the body is silently ignored rather than rejected. To rename a list, create a new one and delete the old.\n\nNote the merge is shallow: sending `prices` replaces the whole array rather than merging entries into it.\n\n#### Signature\n\n```http\nPUT /storefront/pricing/price-lists/{name} (name: string, body) -> The updated price list\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A `name` in the body is discarded — the path wins.\n- `prices` and other arrays are replaced, not merged.\n- Updates are authored as `system`, not the calling user.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRICE_LIST_NOT_FOUND | The requested resource was not found | No price list in the org has that name. | List them with `GET /storefront/pricing/price-lists`. The server knows which one was missing but does not tell you: the underlying error is a plain `Error`, and the global filter rewrites its message to the generic text for anything that is not an `HttpException`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/pricing/price-lists/{name}`\n- `DELETE /storefront/pricing/price-lists/{name}`","requestBody":{"description":"Fields to change. Omitted fields are left as they are.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Unique name. Doubles as the identifier in the path and **cannot be changed** by an update.","example":"wholesale"},"title":{"type":"string","example":"Wholesale pricing"},"status":{"type":"string","description":"Only `active` lists are considered when resolving a customer's pricing.","example":"active"},"priority":{"type":"number","description":"Higher wins when several lists target the same customer. Lists are resolved in descending priority.","example":10},"currency":{"type":"string","example":"USD"},"prices":{"type":"array","description":"The prices this list sets.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","example":9},"minQuantity":{"type":"number","description":"Quantity at which this price starts applying.","example":12}}}},"customerGroups":{"type":"array","items":{"type":"string"},"description":"Groups this list targets.","example":["wholesale"]},"customerEmails":{"type":"array","items":{"type":"string"},"description":"Individual customers this list targets."}}},"examples":{"deactivate":{"summary":"Take a list out of service","value":{"status":"inactive"}},"replacePrices":{"summary":"Replace the price table","description":"`prices` is replaced wholesale — send every entry you want to keep.","value":{"prices":[{"sku":"DRK-COLA-330","price":8.5,"minQuantity":12},{"sku":"DRK-COLA-500","price":11,"minQuantity":12}]}}}}}}},"delete":{"operationId":"PricingController_deletePriceList","summary":"Delete a price list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Price list `name`, not its `sk`.","example":"wholesale"}],"responses":{"200":{"description":"Confirmation of the delete","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true}}},"example":{"success":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The requested resource was not found — No price list in the org has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"The requested resource was not found","path":"/storefront/pricing/price-lists/{name}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Permanently deletes a price list. This is a hard delete, not a soft one — the record is removed rather than flagged, so there is nothing to restore.\n\nCustomers currently priced by this list fall back to whatever the pipeline resolves next: another targeting list, or the product's own price. Deactivate the list first (`status: \"inactive\"`) if you want to see that effect before it becomes irreversible.\n\n#### Signature\n\n```http\nDELETE /storefront/pricing/price-lists/{name} (name: string) -> Confirmation of the delete\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Hard delete — the record is gone, not archived.\n- Prefer setting `status: \"inactive\"` to take a list out of service reversibly.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRICE_LIST_NOT_FOUND | The requested resource was not found | No price list in the org has that name. | List them with `GET /storefront/pricing/price-lists`. The server knows which one was missing but does not tell you: the underlying error is a plain `Error`, and the global filter rewrites its message to the generic text for anything that is not an `HttpException`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /storefront/pricing/price-lists/{name}`"}},"/storefront/pricing/preview-as-customer":{"post":{"operationId":"PricingController_previewAsCustomer","summary":"Preview a price as another customer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The price as that customer would see it","content":{"application/json":{"schema":{"type":"object","properties":{"originalPrice":{"type":"number","description":"Base price before the pipeline ran.","example":12},"finalPrice":{"type":"number","description":"Unit price after every rule.","example":9.6},"unitPrice":{"type":"number","description":"Same as `finalPrice`. Kept for clients that read this name.","example":9.6},"totalPrice":{"type":"number","description":"`finalPrice × quantity`.","example":19.2},"discount":{"type":"number","description":"Absolute amount taken off the unit price.","example":2.4},"discountPercent":{"type":"number","example":20},"quantity":{"type":"number","example":2},"currency":{"type":"string","description":"From the product, defaulting to `USD`.","example":"USD"},"appliedDiscounts":{"type":"array","description":"Every rule that contributed, in the order it applied.","items":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"summer-sale"},"amount":{"type":"number","example":2.4}}}},"appliedRule":{"type":"object","additionalProperties":true,"description":"The winning price rule, when one won."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The requested resource was not found — The `sku` does not resolve to a product in the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"The requested resource was not found","path":"/storefront/pricing/preview-as-customer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Pricing"],"description":"Prices a product as a specified customer rather than the caller — the operator tool for answering \"what does this cost for that wholesale account?\" without signing in as them.\n\nOmitting `customerId` prices it anonymously, which is the useful way to see the public price.\n\n#### Signature\n\n```http\nPOST /storefront/pricing/preview-as-customer (body) -> The price as that customer would see it\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This exposes another customer's negotiated pricing — treat it as an operator endpoint and do not proxy it to a storefront.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PRODUCT_NOT_FOUND | The requested resource was not found | The `sku` does not resolve to a product in the org. | Check the SKU with `GET /storefront/products`. The server knows which one was missing but does not tell you: the underlying error is a plain `Error`, and the global filter rewrites its message to the generic text for anything that is not an `HttpException`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/pricing/calculate`","requestBody":{"description":"What to price, and as whom.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku"],"properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"quantity":{"type":"number","default":1,"example":12},"customerId":{"type":"string","description":"Customer to price as. Omit for anonymous pricing.","example":"cus_4821"}}},"examples":{"asCustomer":{"summary":"Price as a wholesale account","value":{"sku":"DRK-COLA-330","quantity":12,"customerId":"cus_4821"}},"anonymous":{"summary":"Price as an anonymous visitor","value":{"sku":"DRK-COLA-330","quantity":1}}}}}}}},"/storefront/tax/calculate":{"post":{"operationId":"TaxController_calculateCartTax","summary":"Calculate tax for a cart","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The cart to price tax for.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","description":"Cart lines. Used to derive `taxableAmount` when `subtotal` is absent.","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"price":{"type":"number","description":"Unit price. Defaults to `0` when missing.","example":12},"quantity":{"type":"number","description":"Defaults to `1` when missing.","example":2}}}},"shippingAddress":{"type":"object","description":"Destination address. Rates are resolved by country → state → county / city → zip prefix.","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"state":{"type":"string","example":"TX"},"county":{"type":"string","example":"Dallas County"},"city":{"type":"string","example":"Dallas"},"zip":{"type":"string","description":"`postalCode` is accepted as an alias.","example":"75201"},"street1":{"type":"string"},"street2":{"type":"string"}}},"shippingCost":{"type":"number","description":"Taxed by `shipping` / `all` rates when an address is supplied.","example":4.99},"subtotal":{"type":"number","description":"When present, used as `taxableAmount` directly instead of summing `items`.","example":129.99},"businessLocationId":{"type":"string","description":"Venue slug. Enables venue-restricted rates and the flat venue fallback.","example":"downtown"},"customerId":{"type":"string","description":"Checked for a tax exemption.","example":"cus_4821"},"currency":{"type":"string","description":"Echoed back. Default `USD`.","example":"USD"},"date":{"type":"string","format":"date","description":"Order date the rates must be effective on. Default today."}}},"examples":{"shipped":{"summary":"Ship to Dallas, TX","value":{"subtotal":129.99,"shippingCost":4.99,"shippingAddress":{"country":"US","state":"TX","city":"Dallas","zip":"75201"}}},"inStore":{"summary":"In-person at a venue","value":{"items":[{"sku":"DRK-COLA-330","price":12,"quantity":2}],"businessLocationId":"downtown"}}}}}},"responses":{"201":{"description":"The tax result","content":{"application/json":{"schema":{"type":"object","properties":{"taxAmount":{"type":"number","description":"Tax due, including any shipping tax.","example":10.72},"taxRate":{"type":"number","description":"Effective rate on the goods base as a **fraction** (0.0825 = 8.25%).","example":0.0825},"taxableAmount":{"type":"number","description":"The goods/services base tax was computed against.","example":129.99},"taxName":{"type":"string","description":"Receipt label for the whole stack.","example":"TX State + Dallas City"},"breakdown":{"type":"array","description":"One line per applied rate, in application order. Amounts add up to `taxAmount`.","items":{"type":"object","properties":{"name":{"type":"string","description":"Receipt label (`taxName`).","example":"TX State"},"rate":{"type":"number","description":"Percent.","example":6.25},"amount":{"type":"number","example":8.12},"compound":{"type":"boolean","description":"Present and `true` when the line was computed on base + prior tax."}}}},"source":{"type":"string","enum":["jurisdiction","location","exempt","none"],"description":"`jurisdiction` = `sf_tax_rate` rows matched; `location` = venue flat rate; `exempt` = customer is tax exempt; `none` = nothing configured."},"exempt":{"type":"boolean","example":false},"currency":{"type":"string","description":"Echo of the request `currency`, default `USD`.","example":"USD"},"configured":{"type":"boolean","description":"`false` when tax is `0` only because nothing is configured for the input."},"shippingTax":{"type":"number","description":"Tax on `shippingCost`, already included in `taxAmount`.","example":0.41}}},"example":{"taxAmount":11.13,"taxRate":0.0825,"taxableAmount":129.99,"shippingTax":0.41,"taxName":"TX State + Dallas City","breakdown":[{"name":"TX State","rate":6.25,"amount":8.12},{"name":"Dallas City","rate":2,"amount":2.6},{"name":"TX State (shipping)","rate":6.25,"amount":0.31},{"name":"Dallas City (shipping)","rate":2,"amount":0.1}],"source":"jurisdiction","exempt":false,"currency":"USD","configured":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Tax"],"description":"Tax due on a cart from its lines and destination. `taxableAmount` is `subtotal`, or `price × quantity` summed across `items` when `subtotal` is absent. When `shippingAddress` is present and jurisdiction rows match it, the cart is taxed by destination; otherwise by the venue named in `businessLocationId`. `shippingCost` is taxed separately by the rows whose `appliesTo` is `shipping` or `all`, and that tax is included in `taxAmount` (also reported as `shippingTax`). A tax-exempt `customerId` yields `0`.\n\n**How a rate is chosen.** Active `sf_tax_rate` rows effective on the order date are kept when every jurisdiction field the row specifies equals the address field (case-insensitive; `zipPrefix` is a prefix match on `zip`) and, if the row has a `businessLocationId`, it equals the order's venue. Matches are applied in `priority` order: a non-compound rate taxes the taxable base, a compound rate taxes base + tax already applied. Every line is rounded to the cent and the tax is the sum of the lines. When no row matches, the venue's `location.taxRate` is used; when there is no venue rate either, tax is `0` and `configured` is `false`.\n\n#### Signature\n\n```http\nPOST /storefront/tax/calculate (body) -> The tax result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- `taxRate` is a fraction (0.0825) for backwards compatibility; `breakdown[].rate` and `POST /storefront/tax/rate` use percent.\n- With neither a matching address nor a venue rate the result is `taxAmount: 0, configured: false` — check `configured` before treating `0` as \"no tax due\".\n- Rates are records: create / edit them with the repository API on `datatype: \"sf_tax_rate\"`.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/tax/rate`\n- `GET /storefront/tax/rates/resolve`\n- `POST /storefront/checkout-cart`"}},"/storefront/tax/rate":{"post":{"operationId":"TaxController_getTaxRateForLocation","summary":"Get the tax rate stack for an address","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The address to resolve.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["country"],"properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"state":{"type":"string","example":"TX"},"county":{"type":"string"},"city":{"type":"string","example":"Dallas"},"zip":{"type":"string","example":"75201"},"businessLocationId":{"type":"string","description":"Include rates restricted to this venue."},"date":{"type":"string","format":"date","description":"Default today."},"appliesTo":{"type":"string","enum":["goods","services","shipping","all"],"description":"Filter to rates for this charge type. Default: everything except shipping-only rates."}}},"example":{"country":"US","state":"TX","city":"Dallas","zip":"75201"}}}},"responses":{"201":{"description":"The resolved stack","content":{"application/json":{"schema":{"type":"object","properties":{"rate":{"type":"number","description":"Combined effective **percent** of the stack (compounding included).","example":8.25},"taxName":{"type":"string","example":"TX State + Dallas City"},"jurisdiction":{"type":"string","description":"The address you sent, joined.","example":"Dallas, TX, US"},"rates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"`sf_tax_rate` record `sk`."},"name":{"type":"string","example":"Texas State Sales Tax"},"taxName":{"type":"string","example":"TX State"},"rate":{"type":"number","description":"Percent.","example":6.25},"compound":{"type":"boolean","example":false},"priority":{"type":"number","example":1},"appliesTo":{"type":"string","enum":["goods","services","shipping","all"]},"jurisdictionType":{"type":"string","enum":["country","state","county","city","zip","custom"]},"jurisdiction":{"type":"string","description":"The row's jurisdiction fields joined, e.g. `US / TX / Dallas`."},"businessLocationId":{"type":"string","description":"Present when the row is restricted to one venue."}}},"description":"Matched rows in application order."},"businessLocationId":{"type":"string"},"date":{"type":"string","format":"date","description":"The date the rows had to be effective on."},"configured":{"type":"boolean","description":"`true` when at least one row matched."}}},"example":{"rate":8.25,"taxName":"TX State + Dallas City","jurisdiction":"Dallas, TX, US","rates":[{"id":"a1b2","name":"Texas State Sales Tax","taxName":"TX State","rate":6.25,"compound":false,"priority":1,"appliesTo":"all","jurisdictionType":"state","jurisdiction":"US / TX"},{"id":"c3d4","name":"Dallas City Sales Tax","taxName":"Dallas City","rate":2,"compound":false,"priority":2,"appliesTo":"all","jurisdictionType":"city","jurisdiction":"US / TX / Dallas"}],"date":"2026-09-02","configured":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Tax"],"description":"Resolves the `sf_tax_rate` rows that apply to an address and returns them in application order with the combined percent. Same result as `GET /storefront/tax/rates/resolve`, as a POST body.\n\n**How a rate is chosen.** Active `sf_tax_rate` rows effective on the order date are kept when every jurisdiction field the row specifies equals the address field (case-insensitive; `zipPrefix` is a prefix match on `zip`) and, if the row has a `businessLocationId`, it equals the order's venue. Matches are applied in `priority` order: a non-compound rate taxes the taxable base, a compound rate taxes base + tax already applied. Every line is rounded to the cent and the tax is the sum of the lines. When no row matches, the venue's `location.taxRate` is used; when there is no venue rate either, tax is `0` and `configured` is `false`.\n\n#### Signature\n\n```http\nPOST /storefront/tax/rate (body) -> The resolved stack\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- `rate` here is a **percent**; `POST /storefront/tax/calculate` returns `taxRate` as a fraction.\n- An address with no matching row returns `rate: 0, rates: [], configured: false`.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/tax/rates/resolve`\n- `POST /storefront/tax/calculate`"}},"/storefront/tax/rates/resolve":{"get":{"operationId":"TaxController_resolveRates","summary":"Check which tax rates apply to an address","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"country","required":false,"in":"query","schema":{"type":"string"},"description":"ISO 3166-1 alpha-2.","example":"US"},{"name":"state","required":false,"in":"query","schema":{"type":"string"},"example":"TX"},{"name":"county","required":false,"in":"query","schema":{"type":"string"}},{"name":"city","required":false,"in":"query","schema":{"type":"string"},"example":"Dallas"},{"name":"zip","required":false,"in":"query","schema":{"type":"string"},"example":"75201"},{"name":"businessLocationId","required":false,"in":"query","description":"Venue slug; includes rates restricted to it.","schema":{"type":"string"}},{"name":"date","required":false,"in":"query","description":"YYYY-MM-DD the rows must be effective on. Default today.","schema":{"type":"string"}},{"name":"appliesTo","required":false,"in":"query","schema":{"type":"string"},"description":"`goods` | `services` | `shipping` | `all`."}],"responses":{"200":{"description":"The resolved stack","content":{"application/json":{"schema":{"type":"object","properties":{"rate":{"type":"number","description":"Combined effective **percent** of the stack (compounding included).","example":8.25},"taxName":{"type":"string","example":"TX State + Dallas City"},"jurisdiction":{"type":"string","description":"The address you sent, joined.","example":"Dallas, TX, US"},"rates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"`sf_tax_rate` record `sk`."},"name":{"type":"string","example":"Texas State Sales Tax"},"taxName":{"type":"string","example":"TX State"},"rate":{"type":"number","description":"Percent.","example":6.25},"compound":{"type":"boolean","example":false},"priority":{"type":"number","example":1},"appliesTo":{"type":"string","enum":["goods","services","shipping","all"]},"jurisdictionType":{"type":"string","enum":["country","state","county","city","zip","custom"]},"jurisdiction":{"type":"string","description":"The row's jurisdiction fields joined, e.g. `US / TX / Dallas`."},"businessLocationId":{"type":"string","description":"Present when the row is restricted to one venue."}}},"description":"Matched rows in application order."},"businessLocationId":{"type":"string"},"date":{"type":"string","format":"date","description":"The date the rows had to be effective on."},"configured":{"type":"boolean","description":"`true` when at least one row matched."}}},"example":{"rate":8.25,"taxName":"TX State + Dallas City","jurisdiction":"Dallas, TX, US","rates":[{"id":"a1b2","name":"Texas State Sales Tax","taxName":"TX State","rate":6.25,"compound":false,"priority":1,"appliesTo":"all","jurisdictionType":"state","jurisdiction":"US / TX"},{"id":"c3d4","name":"Dallas City Sales Tax","taxName":"Dallas City","rate":2,"compound":false,"priority":2,"appliesTo":"all","jurisdictionType":"city","jurisdiction":"US / TX / Dallas"}],"date":"2026-09-02","configured":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Tax"],"description":"Operator configuration check: the `sf_tax_rate` rows that would be applied to an order shipped to (or taken at) the given address / venue on the given date, in application order, with the combined percent. Use it after creating or editing rate records to confirm they match the way you expect.\n\n**How a rate is chosen.** Active `sf_tax_rate` rows effective on the order date are kept when every jurisdiction field the row specifies equals the address field (case-insensitive; `zipPrefix` is a prefix match on `zip`) and, if the row has a `businessLocationId`, it equals the order's venue. Matches are applied in `priority` order: a non-compound rate taxes the taxable base, a compound rate taxes base + tax already applied. Every line is rounded to the cent and the tax is the sum of the lines. When no row matches, the venue's `location.taxRate` is used; when there is no venue rate either, tax is `0` and `configured` is `false`.\n\n#### Signature\n\n```http\nGET /storefront/tax/rates/resolve (country?: string, state?: string, county?: string, city?: string, zip?: string, businessLocationId?: string, date?: string, appliesTo?: string) -> The resolved stack\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Every query parameter is optional; with none, only rows that specify no jurisdiction (custom rates) can match.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/tax/rate`"}},"/storefront/tax/product":{"post":{"operationId":"TaxController_calculateProductTax","summary":"Calculate tax for one product","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The tax result","content":{"application/json":{"schema":{"type":"object","properties":{"taxAmount":{"type":"number","description":"Tax due, including any shipping tax.","example":10.72},"taxRate":{"type":"number","description":"Effective rate on the goods base as a **fraction** (0.0825 = 8.25%).","example":0.0825},"taxableAmount":{"type":"number","description":"The goods/services base tax was computed against.","example":129.99},"taxName":{"type":"string","description":"Receipt label for the whole stack.","example":"TX State + Dallas City"},"breakdown":{"type":"array","description":"One line per applied rate, in application order. Amounts add up to `taxAmount`.","items":{"type":"object","properties":{"name":{"type":"string","description":"Receipt label (`taxName`).","example":"TX State"},"rate":{"type":"number","description":"Percent.","example":6.25},"amount":{"type":"number","example":8.12},"compound":{"type":"boolean","description":"Present and `true` when the line was computed on base + prior tax."}}}},"source":{"type":"string","enum":["jurisdiction","location","exempt","none"],"description":"`jurisdiction` = `sf_tax_rate` rows matched; `location` = venue flat rate; `exempt` = customer is tax exempt; `none` = nothing configured."},"exempt":{"type":"boolean","example":false},"currency":{"type":"string","description":"Echo of the request `currency`, default `USD`.","example":"USD"},"configured":{"type":"boolean","description":"`false` when tax is `0` only because nothing is configured for the input."}}},"example":{"taxAmount":1.5,"taxRate":0.0625,"taxableAmount":24,"taxName":"TX State","breakdown":[{"name":"TX State","rate":6.25,"amount":1.5}],"source":"jurisdiction","exempt":false,"currency":"USD","configured":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Tax"],"description":"The single-line counterpart to the cart calculation: `price × quantity` (default `1`) is the taxable base, resolved by `shippingAddress` when rows match it, else by the venue in `businessLocationId`. `productId` / `sku` are informational — the product is not looked up, so `price` must be supplied.\n\n**How a rate is chosen.** Active `sf_tax_rate` rows effective on the order date are kept when every jurisdiction field the row specifies equals the address field (case-insensitive; `zipPrefix` is a prefix match on `zip`) and, if the row has a `businessLocationId`, it equals the order's venue. Matches are applied in `priority` order: a non-compound rate taxes the taxable base, a compound rate taxes base + tax already applied. Every line is rounded to the cent and the tax is the sum of the lines. When no row matches, the venue's `location.taxRate` is used; when there is no venue rate either, tax is `0` and `configured` is `false`.\n\n#### Signature\n\n```http\nPOST /storefront/tax/product (body) -> The tax result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Passing `sku` without `price` taxes a base of `0` — the SKU is not resolved to a price.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/tax/calculate`","requestBody":{"description":"The product line to price tax for.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["price"],"properties":{"price":{"type":"number","description":"Unit price. Required — the product is not looked up to find it.","example":12},"quantity":{"type":"number","default":1,"example":2},"productId":{"type":"string","description":"Informational."},"sku":{"type":"string","description":"Informational.","example":"DRK-COLA-330"},"shippingAddress":{"type":"object","description":"Destination address. Rates are resolved by country → state → county / city → zip prefix.","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"US"},"state":{"type":"string","example":"TX"},"county":{"type":"string","example":"Dallas County"},"city":{"type":"string","example":"Dallas"},"zip":{"type":"string","description":"`postalCode` is accepted as an alias.","example":"75201"},"street1":{"type":"string"},"street2":{"type":"string"}}},"businessLocationId":{"type":"string","example":"downtown"},"customerId":{"type":"string","description":"Checked for a tax exemption."},"currency":{"type":"string","example":"USD"},"date":{"type":"string","format":"date"}}},"example":{"sku":"DRK-COLA-330","price":12,"quantity":2,"shippingAddress":{"country":"US","state":"TX","city":"Austin"}}}}}}},"/storefront/tax/check-exempt":{"post":{"operationId":"TaxController_checkTaxExempt","summary":"Check tax exemption status","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The exemption state","content":{"application/json":{"schema":{"type":"object","properties":{"exempt":{"type":"boolean"},"reason":{"type":"string","nullable":true,"example":"Reseller"},"certificate":{"type":"string","nullable":true,"example":"EX-99182"},"customerId":{"type":"string","nullable":true,"example":"cus_4821"},"configured":{"type":"boolean","description":"`false` when no customer matched."}}},"example":{"exempt":true,"reason":"Reseller","certificate":"EX-99182","customerId":"cus_4821","configured":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Tax"],"description":"Reads the customer record's `taxExempt` flag (with `taxExemptReason` and `taxExemptCertificate`). Looks the customer up by `customerId`, falling back to `email`. `configured: false` means no customer was found for the input — not that the customer is taxable.\n\n#### Signature\n\n```http\nPOST /storefront/tax/check-exempt (body) -> The exemption state\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/tax/apply-exemption`\n- `POST /storefront/tax/remove-exemption`","requestBody":{"description":"Who to check.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Customer `sk`. Either this or `email`.","example":"cus_4821"},"email":{"type":"string","description":"Customer email, used when `customerId` is absent.","example":"ada@example.com"},"exemptionNumber":{"type":"string","description":"Informational."}}},"example":{"customerId":"cus_4821"}}}}}},"/storefront/tax/apply-exemption":{"post":{"operationId":"TaxController_applyTaxExemption","summary":"Apply a tax exemption to a customer","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The stored exemption state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"exempt":{"type":"boolean"},"reason":{"type":"string","nullable":true,"example":"Reseller"},"certificate":{"type":"string","nullable":true,"example":"EX-99182"},"customerId":{"type":"string","nullable":true,"example":"cus_4821"}}},"example":{"success":true,"customerId":"cus_4821","exempt":true,"reason":"Reseller","certificate":"EX-99182"}}}},"400":{"description":"customerId or email is required — Neither `customerId` nor `email` is supplied.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"customerId or email is required","path":"/storefront/tax/apply-exemption","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Customer \"…\" not found — No customer matches `customerId` (any id) or `email`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Customer \"…\" not found","path":"/storefront/tax/apply-exemption","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Tax"],"description":"Sets `taxExempt: true` on the customer record and stores the reason and certificate number. From then on `computeOrderTax` / `calculate` / `product` charge this customer no tax (`source: \"exempt\"`).\n\n#### Signature\n\n```http\nPOST /storefront/tax/apply-exemption (body) -> The stored exemption state\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | customerId or email is required | Neither `customerId` nor `email` is supplied. | Send one of them. |\n| `404` | — | Customer \"…\" not found | No customer matches `customerId` (any id) or `email`. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/tax/check-exempt`\n- `POST /storefront/tax/remove-exemption`","requestBody":{"description":"The exemption to record.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Customer `sk`. Either this or `email`.","example":"cus_4821"},"email":{"type":"string","description":"Customer email, used when `customerId` is absent.","example":"ada@example.com"},"exemptionNumber":{"type":"string","description":"Certificate / permit number. Alias of `certificate`.","example":"EX-99182"},"certificate":{"type":"string","description":"Certificate / permit number."},"reason":{"type":"string","description":"Why the customer is exempt.","example":"Reseller"},"state":{"type":"string","description":"Used to build a default reason when `reason` is absent.","example":"TX"}}},"example":{"customerId":"cus_4821","exemptionNumber":"EX-99182","reason":"Reseller"}}}}}},"/storefront/tax/remove-exemption":{"post":{"operationId":"TaxController_removeTaxExemption","summary":"Remove a tax exemption from a customer","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The cleared exemption state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"exempt":{"type":"boolean"},"reason":{"type":"string","nullable":true,"example":"Reseller"},"certificate":{"type":"string","nullable":true,"example":"EX-99182"},"customerId":{"type":"string","nullable":true,"example":"cus_4821"}}},"example":{"success":true,"customerId":"cus_4821","exempt":false,"reason":null,"certificate":null}}}},"400":{"description":"customerId or email is required — Neither `customerId` nor `email` is supplied.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"customerId or email is required","path":"/storefront/tax/remove-exemption","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Customer \"…\" not found — No customer matches `customerId` (any id) or `email`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Customer \"…\" not found","path":"/storefront/tax/remove-exemption","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Tax"],"description":"Clears `taxExempt`, `taxExemptReason` and `taxExemptCertificate` on the customer record.\n\n#### Signature\n\n```http\nPOST /storefront/tax/remove-exemption (body) -> The cleared exemption state\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | customerId or email is required | Neither `customerId` nor `email` is supplied. | Send one of them. |\n| `404` | — | Customer \"…\" not found | No customer matches `customerId` (any id) or `email`. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/tax/apply-exemption`","requestBody":{"description":"Which customer.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Customer `sk`. Either this or `email`.","example":"cus_4821"},"email":{"type":"string","description":"Customer email, used when `customerId` is absent.","example":"ada@example.com"},"exemptionId":{"type":"string","description":"Informational."}}},"example":{"customerId":"cus_4821"}}}}}},"/storefront/rental/config":{"get":{"operationId":"RentalController_getConfig","summary":"Get rental configuration","description":"Returns the org's rental settings — periods, fees and defaults. Omit `name` for the default configuration.\n\n#### Signature\n\n```http\nGET /storefront/rental/config (name?: string) -> The rental configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/items`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":false,"in":"query","description":"Named configuration. Omit for the default.","schema":{"type":"string"},"example":"default"}],"responses":{"200":{"description":"The rental configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/config","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/items":{"get":{"operationId":"RentalController_listRentalItems","summary":"List rental items","description":"Lists the items available to rent, with optional status filtering and paging. Items in `maintenance` are not rentable but still listed.\n\n#### Signature\n\n```http\nGET /storefront/rental/items (status?: string, page?: integer, pageSize?: integer) -> A page of rental items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/items/{sku}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["active","inactive","maintenance"]},"example":"active"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of rental items","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A rentable item (`sf_rental_item`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}},"total":{"type":"integer","example":24}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/items","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/items/{sku}":{"get":{"operationId":"RentalController_getRentalItem","summary":"Get a rental item","description":"Fetches one rentable item by SKU, including its rates, deposit and any customer restrictions.\n\n#### Signature\n\n```http\nGET /storefront/rental/items/{sku} (sku: string) -> The rental item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/items/{sku}/availability`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"sku","required":true,"in":"path","description":"Rental item SKU.","schema":{"type":"string"},"example":"CAM-RED-KOMODO"}],"responses":{"200":{"description":"The rental item","content":{"application/json":{"schema":{"type":"object","description":"A rentable item (`sf_rental_item`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental item not found — The SKU does not resolve to a rental item.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental item not found","path":"/storefront/rental/items/{sku}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/items/{sku}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/items/{sku}/availability":{"get":{"operationId":"RentalController_checkAvailability","summary":"Check rental availability","description":"Reports whether an item is free for a date range, accounting for existing bookings.\n\nAvailability is checked again when the rental is created, so a positive answer here is not a reservation — another customer can still book the window first.\n\n#### Signature\n\n```http\nGET /storefront/rental/items/{sku}/availability (sku: string, startDate?: string, endDate?: string, quantity?: integer) -> Availability for the window\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not a hold. The authoritative check happens at `POST /storefront/rental`, which returns `409` if the window has gone.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /storefront/rental/items/{sku}/price`\n- `POST /storefront/rental`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"sku","required":true,"in":"path","description":"Rental item SKU.","schema":{"type":"string"},"example":"CAM-RED-KOMODO"},{"name":"startDate","required":true,"in":"query","description":"Start of the window, ISO 8601.","schema":{"type":"string"},"example":"2026-09-01T09:00:00.000Z"},{"name":"endDate","required":true,"in":"query","description":"End of the window, ISO 8601.","schema":{"type":"string"},"example":"2026-09-04T17:00:00.000Z"},{"name":"quantity","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1}],"responses":{"200":{"description":"Availability for the window","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental item not found — The SKU does not resolve to a rental item.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental item not found","path":"/storefront/rental/items/{sku}/availability","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/items/{sku}/availability","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/items/{sku}/price":{"post":{"operationId":"RentalController_calculatePrice","summary":"Calculate a rental price","description":"Prices a rental for a date range, including any optional fees the customer selects — insurance, cleaning, delivery. The rate band is chosen from the length of the window, so a week may cost less than seven days.\n\n#### Signature\n\n```http\nPOST /storefront/rental/items/{sku}/price (sku: string, body) -> The priced rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /storefront/rental`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"sku","required":true,"in":"path","description":"Rental item SKU.","schema":{"type":"string"},"example":"CAM-RED-KOMODO"}],"responses":{"201":{"description":"The priced rental","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental item not found — The SKU does not resolve to a rental item.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental item not found","path":"/storefront/rental/items/{sku}/price","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/items/{sku}/price","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"The window and options to price.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["startDate","endDate"],"properties":{"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"quantity":{"type":"number","default":1,"example":1},"optionalFees":{"type":"array","items":{"type":"string"},"description":"Codes of the optional fees to include.","example":["insurance"]}}},"example":{"startDate":"2026-09-01T09:00:00.000Z","endDate":"2026-09-04T17:00:00.000Z","quantity":1,"optionalFees":["insurance"]}}}}}},"/storefront/rental":{"post":{"operationId":"RentalController_createRental","summary":"Create a rental booking","description":"Books an item for a date range. The rental starts `pending`.\n\nThree checks run in order, and each has its own failure: the item must exist, it must be free for the window, and the customer must satisfy the item's restrictions. **Availability is re-checked here**, so a window that was free when you queried it can still be lost to another booking — a `409` means exactly that.\n\nFulfilment and return are described separately, so an item can be delivered and collected, picked up and dropped off, or any combination.\n\n#### Signature\n\n```http\nPOST /storefront/rental (body) -> The created rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The `403` body carries an `errors` array naming each failed restriction, rather than the standard error envelope.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |\n| `409` | NOT_AVAILABLE | Item not available for selected dates | The item is already booked for some part of the window. | Re-check with the availability endpoint and offer the customer another window. Availability is not held between checking and booking. |\n| `403` | RESTRICTIONS_NOT_MET | Customer restrictions not met | The customer fails one of the item's restrictions — age or location, typically. | Validate first with `POST /storefront/rental/items/{sku}/validate-restrictions`, which reports every failure at once. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/items/{sku}/availability`\n- `PUT /storefront/rental/{rentalId}/confirm`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The created rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Customer restrictions not met — The customer fails one of the item's restrictions — age or location, typically.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"message":"Customer restrictions not met","errors":[{"type":"age","message":"Must be 21 or older"}]}}}},"404":{"description":"Rental item not found — The SKU does not resolve to a rental item.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental item not found","path":"/storefront/rental","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Item not available for selected dates — The item is already booked for some part of the window.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Item not available for selected dates","path":"/storefront/rental","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"The item, window, customer and logistics.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","customer","startDate","endDate"],"properties":{"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","default":1,"example":1},"customer":{"type":"object","required":["firstName","lastName","email","phone"],"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the window is expressed in.","example":"America/New_York"},"fulfillment":{"type":"object","description":"How the customer gets the item.","properties":{"type":{"type":"string","enum":["pickup","delivery"],"example":"delivery"},"address":{"type":"object","additionalProperties":true,"description":"Postal address."},"instructions":{"type":"string","example":"Leave with the doorman"}}},"return":{"type":"object","description":"How the item comes back. Independent of `fulfillment`.","properties":{"type":{"type":"string","enum":["dropoff","pickup"],"example":"dropoff"},"address":{"type":"object","additionalProperties":true,"description":"Postal address."},"instructions":{"type":"string"}}},"optionalFees":{"type":"array","items":{"type":"string"},"example":["insurance"]},"customerNotes":{"type":"string","example":"Shooting outdoors, may need the rain cover"}}},"examples":{"pickup":{"summary":"Customer collects and returns in person","value":{"sku":"CAM-RED-KOMODO","customer":{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com","phone":"+15551234567"},"startDate":"2026-09-01T09:00:00.000Z","endDate":"2026-09-04T17:00:00.000Z","fulfillment":{"type":"pickup"},"return":{"type":"dropoff"}}},"delivered":{"summary":"Delivered out, collected back","value":{"sku":"CAM-RED-KOMODO","quantity":1,"customer":{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com","phone":"+15551234567"},"startDate":"2026-09-01T09:00:00.000Z","endDate":"2026-09-04T17:00:00.000Z","fulfillment":{"type":"delivery","address":{"line1":"12 Ada Way","city":"London"},"instructions":"Leave with the doorman"},"return":{"type":"pickup"},"optionalFees":["insurance"]}}}}}}},"get":{"operationId":"RentalController_listRentals","summary":"List rentals","description":"Lists rental bookings with filters and paging. Filter by customer, item, status or start-date range.\n\n#### Signature\n\n```http\nGET /storefront/rental (status?: string, customerEmail?: string, sku?: string, startDateFrom?: string, startDateTo?: string, page?: integer, pageSize?: integer) -> A page of rentals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/status/overdue`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"]},"example":"active"},{"name":"customerEmail","required":false,"in":"query","schema":{"type":"string"},"example":"ada@example.com"},{"name":"sku","required":false,"in":"query","schema":{"type":"string"},"example":"CAM-RED-KOMODO"},{"name":"startDateFrom","required":false,"in":"query","schema":{"type":"string"},"description":"Lower bound on start date.","example":"2026-09-01"},{"name":"startDateTo","required":false,"in":"query","schema":{"type":"string"},"description":"Upper bound on start date.","example":"2026-09-30"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of rentals","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}},"total":{"type":"integer","example":18}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}":{"get":{"operationId":"RentalController_getRental","summary":"Get a rental","description":"Fetches one rental with its window, customer, deposit state and both condition records.\n\n#### Signature\n\n```http\nGET /storefront/rental/{rentalId} (rentalId: string) -> The rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/status/overdue":{"get":{"operationId":"RentalController_getOverdueRentals","summary":"Get overdue rentals","description":"Every active rental past its end date and not yet returned. The chase list.\n\n#### Signature\n\n```http\nGET /storefront/rental/status/overdue () -> Overdue rentals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Derived from the end date at request time. Marking a rental `overdue` with the status endpoint is a separate, manual flag.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/status/due-today`\n- `PUT /storefront/rental/{rentalId}/overdue`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Overdue rentals","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/status/overdue","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/status/due-today":{"get":{"operationId":"RentalController_getRentalsDueToday","summary":"Get rentals due today","description":"Rentals whose window ends today — what a counter expects back before closing.\n\n#### Signature\n\n```http\nGET /storefront/rental/status/due-today () -> Rentals ending today\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/status/overdue`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Rentals ending today","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/status/due-today","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/confirm":{"put":{"operationId":"RentalController_confirmRental","summary":"Confirm a rental","description":"Moves a pending rental to `confirmed`, committing the booking. This is the point the customer is told the item is theirs for the window.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/confirm (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/ready`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/confirm","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/confirm","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/checkout":{"put":{"operationId":"RentalController_checkOut","summary":"Record check-out condition","description":"Records the item's condition as it leaves, with optional photographs and the name of whoever inspected it.\n\nThis is the **before** half of the evidence a deposit deduction rests on. Without it there is nothing to compare the returned condition against, and a damage claim comes down to one party's word.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/checkout (rentalId: string, body) -> The rental with its check-out record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/checkin`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The rental with its check-out record","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/checkout","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/checkout","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"Condition at the point the item leaves.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["condition","checkedBy"],"properties":{"condition":{"type":"string","enum":["excellent","good","fair","poor"],"example":"excellent"},"notes":{"type":"string","example":"Body and lens both clean, no marks"},"photos":{"type":"array","items":{"type":"string"},"description":"URLs of condition photographs.","example":["https://cdn.appmint.io/rentals/rnt-4821-out-1.jpg"]},"checkedBy":{"type":"string","description":"Who inspected it.","example":"counter@venue.com"}}},"example":{"condition":"excellent","notes":"Body and lens both clean, no marks","photos":["https://cdn.appmint.io/rentals/rnt-4821-out-1.jpg"],"checkedBy":"counter@venue.com"}}}}}},"/storefront/rental/{rentalId}/checkin":{"put":{"operationId":"RentalController_checkIn","summary":"Record check-in condition","description":"Records the item's condition on return, including any damage found. The **after** half of the evidence.\n\nRecording `damages` here does not itself charge anything — add a deposit deduction to do that, and refund the remainder.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/checkin (rentalId: string, body) -> The rental with its check-in record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `damages` is descriptive only. Charge for it with `POST /storefront/rental/{rentalId}/deposit/deduction`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /storefront/rental/{rentalId}/deposit/deduction`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The rental with its check-in record","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/checkin","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/checkin","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"Condition at the point the item comes back.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["condition","checkedBy"],"properties":{"condition":{"type":"string","enum":["excellent","good","fair","poor"],"example":"good"},"notes":{"type":"string","example":"Light scuffing on the base plate"},"photos":{"type":"array","items":{"type":"string"},"example":["https://cdn.appmint.io/rentals/rnt-4821-in-1.jpg"]},"damages":{"type":"string","description":"Description of damage found. Recording it does not charge for it.","example":"Scratched lens housing"},"checkedBy":{"type":"string","example":"counter@venue.com"}}},"example":{"condition":"fair","notes":"Scratched lens housing","damages":"Scratched lens housing","photos":["https://cdn.appmint.io/rentals/rnt-4821-in-1.jpg"],"checkedBy":"counter@venue.com"}}}}}},"/storefront/rental/{rentalId}/complete":{"put":{"operationId":"RentalController_completeRental","summary":"Complete a rental","description":"Closes the rental. Settle the deposit first — refund what is owed and record any deductions — because completion is the end of the lifecycle.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/complete (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/deposit/refund`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/complete","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/complete","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/cancel":{"put":{"operationId":"RentalController_cancelRental","summary":"Cancel a rental","description":"Cancels a rental, recording the reason. Any deposit that was held must be refunded separately — cancelling does not release it.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/cancel (rentalId: string, body) -> The cancelled rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Does not refund the deposit or any payment — issue those explicitly.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/deposit/refund`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The cancelled rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"Why the rental is being cancelled.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Recorded on the rental.","example":"Customer cancelled — shoot postponed"}}},"example":{"reason":"Customer cancelled — shoot postponed"}}}}}},"/storefront/rental/{rentalId}/ready":{"put":{"operationId":"RentalController_markReady","summary":"Mark a rental ready","description":"Marks the item prepared and waiting for collection or dispatch. The signal to a counter that it can be handed over.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/ready (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/picked-up`\n- `PUT /storefront/rental/{rentalId}/delivered`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/ready","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/ready","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/picked-up":{"put":{"operationId":"RentalController_markPickedUp","summary":"Mark a rental picked up","description":"Records that the customer collected the item in person, and notifies them. Use `delivered` instead when the item was sent out.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/picked-up (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/checkout`\n- `PUT /storefront/rental/{rentalId}/delivered`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/picked-up","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/picked-up","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/delivered":{"put":{"operationId":"RentalController_markDelivered","summary":"Mark a rental delivered","description":"Records that the item was delivered to the customer, and notifies them. The delivery counterpart to `picked-up`.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/delivered (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/picked-up`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/delivered","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/delivered","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/active":{"put":{"operationId":"RentalController_markActive","summary":"Mark a rental active","description":"Marks the item as in use by the customer — the state a rental sits in for the body of its window.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/active (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/overdue`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/active","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/active","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/overdue":{"put":{"operationId":"RentalController_markOverdue","summary":"Mark a rental overdue","description":"Flags a rental as overdue. This is a **manual** status change; `GET /storefront/rental/status/overdue` derives its list from dates instead and does not depend on this flag being set.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/overdue (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/status/overdue`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/overdue","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/overdue","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/dropped-off":{"put":{"operationId":"RentalController_markDroppedOff","summary":"Mark a rental dropped off","description":"Records that the customer returned the item, and notifies them. The item is back but not yet inspected — record the condition with `checkin`.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/dropped-off (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/checkin`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/dropped-off","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/dropped-off","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"]}},"/storefront/rental/{rentalId}/returned":{"put":{"operationId":"RentalController_markReturned","summary":"Mark a rental returned","description":"> **Deprecated.** Superseded by `PUT /storefront/rental/{rentalId}/dropped-off`, which it delegates to.\n\nAlias for `dropped-off`, kept for older clients. Prefer `PUT /storefront/rental/{rentalId}/dropped-off` in new work.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/returned (rentalId: string) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/dropped-off`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/returned","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/returned","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"deprecated":true}},"/storefront/rental/{rentalId}/notes":{"put":{"operationId":"RentalController_updateNotes","summary":"Update internal notes","description":"Replaces the rental's internal notes. The status is left unchanged.\n\n**A missing rental is reported oddly here:** the handler returns `{ \"error\": \"Rental not found\" }` with a `200`, rather than the `404` every other rental endpoint raises. Check the body, not the status.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/notes (rentalId: string, body) -> The updated rental, or an error object when the rental does not exist\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Replaces the notes rather than appending — read them first if you want to add to them.\n- Inconsistent with the rest of the controller: no `404` is raised for a missing rental.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /storefront/rental/{rentalId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The updated rental, or an error object when the rental does not exist","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"ok":{"summary":"Updated","value":{"data":{"rentalId":"RNT-4821","internalNotes":"Customer is a regular"}}},"missing":{"summary":"Rental not found — still a 200","value":{"error":"Rental not found"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/notes","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"The notes to store.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["notes"],"properties":{"notes":{"type":"string","description":"Replaces the existing internal notes.","example":"Customer is a regular — waive the late fee if under an hour"}}},"example":{"notes":"Customer is a regular — waive the late fee if under an hour"}}}}}},"/storefront/rental/{rentalId}/payment":{"post":{"operationId":"RentalController_recordPayment","summary":"Record a rental payment","description":"Records a payment against the rental. This books the tender; it does not charge a card — take the payment through a gateway first and record its reference here.\n\n#### Signature\n\n```http\nPOST /storefront/rental/{rentalId}/payment (rentalId: string, body) -> The updated rental\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent — a retry records a second payment.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/deposit/hold`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"201":{"description":"The updated rental","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"The payment to record.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","method"],"properties":{"amount":{"type":"number","example":495},"method":{"type":"string","description":"How it was paid.","example":"card"},"transactionId":{"type":"string","description":"Gateway reference.","example":"pi_3PabcXYZ"}}},"example":{"amount":495,"method":"card","transactionId":"pi_3PabcXYZ"}}}}}},"/storefront/rental/{rentalId}/deposit/hold":{"put":{"operationId":"RentalController_holdDeposit","summary":"Record a deposit hold","description":"Records that a deposit has been held, capturing the gateway references needed to refund it later.\n\n**`transactionId` is what a later refund is issued against.** Without it the refund has no gateway reference to reverse and can only be recorded as a manual adjustment, so capture it at hold time rather than trying to reconstruct it at the end of the rental.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/deposit/hold (rentalId: string, body) -> The rental with its deposit hold recorded\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/deposit/refund`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The rental with its deposit hold recorded","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/deposit/hold","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/deposit/hold","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"The gateway references for the held deposit.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["transactionId"],"properties":{"transactionId":{"type":"string","description":"Reference the refund will be issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string","description":"Gateway charge id, where the provider distinguishes it.","example":"ch_3PabcXYZ"},"paymentGateway":{"type":"string","description":"Which gateway holds it.","example":"stripe"}}},"example":{"transactionId":"pi_3PabcXYZ","chargeId":"ch_3PabcXYZ","paymentGateway":"stripe"}}}}}},"/storefront/rental/{rentalId}/deposit/refund":{"put":{"operationId":"RentalController_refundDeposit","summary":"Refund the deposit","description":"Returns deposit money to the customer through the **original payment gateway**, using the references captured when the deposit was held.\n\nSet `skipPaymentGateway` when the money has already been returned by other means — cash at the counter, or a refund issued directly in the provider's dashboard. That records the refund without attempting to move money a second time.\n\n#### Signature\n\n```http\nPUT /storefront/rental/{rentalId}/deposit/refund (rentalId: string, body) -> The rental with the refund recorded\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Needs the references stored by `deposit/hold`. Without them, use `skipPaymentGateway` and settle outside the platform.\n- The amount is not derived from the deductions — calculate it yourself, or you may refund more than is owed.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/deposit/hold`\n- `POST /storefront/rental/{rentalId}/deposit/deduction`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"200":{"description":"The rental with the refund recorded","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/deposit/refund","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/deposit/refund","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"How much to return, and whether to involve the gateway.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Amount to refund — typically the deposit minus any deductions.","example":175},"skipPaymentGateway":{"type":"boolean","default":false,"description":"Record the refund without calling the gateway, for money already returned another way.","example":false},"reason":{"type":"string","example":"Deposit returned less damage deduction"}}},"examples":{"gateway":{"summary":"Refund through the gateway","value":{"amount":175,"reason":"Deposit returned less damage deduction"}},"manual":{"summary":"Record a refund paid in cash","value":{"amount":250,"skipPaymentGateway":true,"reason":"Returned in cash at the counter"}}}}}}}},"/storefront/rental/{rentalId}/deposit/deduction":{"post":{"operationId":"RentalController_addDepositDeduction","summary":"Deduct from the deposit","description":"Records a deduction against the deposit for damage, late return, cleaning, or anything else the customer is liable for. Each deduction carries its own reason, so the customer can be shown an itemised account.\n\nDeductions reduce what will be refunded; they do not move money on their own. Refund the remainder explicitly once the total is settled.\n\n#### Signature\n\n```http\nPOST /storefront/rental/{rentalId}/deposit/deduction (rentalId: string, body) -> The rental with the deduction recorded\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Call once per distinct charge — several deductions produce an itemised list rather than one opaque total.\n- Nothing checks the total of deductions against the deposit, so it is possible to record more than was held.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |\n| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /storefront/rental/{rentalId}/checkin`\n- `PUT /storefront/rental/{rentalId}/deposit/refund`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"rentalId","required":true,"in":"path","description":"Rental id.","schema":{"type":"string"},"example":"RNT-4821"}],"responses":{"201":{"description":"The rental with the deduction recorded","content":{"application/json":{"schema":{"type":"object","description":"A rental booking (`sf_rental`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"rentalId":{"type":"string","example":"RNT-4821"},"sku":{"type":"string","example":"CAM-RED-KOMODO"},"quantity":{"type":"number","example":1},"status":{"type":"string","enum":["pending","confirmed","ready","picked_up","delivered","active","overdue","dropped_off","returned","completed","cancelled"],"example":"active"},"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-04T17:00:00.000Z"},"timezone":{"type":"string","description":"Timezone the rental window is expressed in.","example":"America/New_York"},"total":{"type":"number","example":495},"amountPaid":{"type":"number","example":495},"deposit":{"type":"object","description":"Deposit state. Held at checkout, deducted from on damage, refunded at completion.","properties":{"amount":{"type":"number","example":250},"held":{"type":"boolean","example":true},"transactionId":{"type":"string","description":"Reference captured at hold time — this is what a later refund is issued against.","example":"pi_3PabcXYZ"},"chargeId":{"type":"string"},"paymentGateway":{"type":"string","example":"stripe"},"deductions":{"type":"array","items":{"type":"object","properties":{"reason":{"type":"string","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}}},"refunded":{"type":"number","example":175}}},"checkOut":{"type":"object","additionalProperties":true,"description":"Condition record from when the item left."},"checkIn":{"type":"object","additionalProperties":true,"description":"Condition record from when the item came back."},"internalNotes":{"type":"string","description":"Operator-only notes."},"customerNotes":{"type":"string","description":"Notes supplied by the customer at booking."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Rental not found — No rental in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Rental not found","path":"/storefront/rental/{rentalId}/deposit/deduction","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The underlying error message, passed through verbatim. — An unexpected failure inside the rental service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The underlying error message, passed through verbatim.","path":"/storefront/rental/{rentalId}/deposit/deduction","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Rentals"],"requestBody":{"description":"What is being deducted, and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason","amount"],"properties":{"reason":{"type":"string","description":"Shown to the customer as the justification.","example":"Scratched lens housing"},"amount":{"type":"number","example":75}}},"example":{"reason":"Scratched lens housing","amount":75}}}}}},"/storefront/rental/items/{sku}/validate-restrictions":{"post":{"operationId":"RentalController_validateRestrictions","summary":"Validate customer restrictions","description":"Checks whether a customer may rent an item — age limits, geographic restrictions, and anything else the item defines.\n\nA failure is reported as `valid: false` with a list of what failed, **not** as an error status, so the booking form can show every problem at once. Call this before taking payment: the same rules are enforced at creation, where they produce a `403` instead.\n\n#### Signature\n\n```http\nPOST /storefront/rental/items/{sku}/validate-restrictions (sku: string, body) -> Whether the customer qualifies, and what failed if not\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- An unknown SKU comes back as `valid: false` with a `type: \"item\"` failure and a `200`, not a `404`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/rental`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"sku","required":true,"in":"path","description":"Rental item SKU.","schema":{"type":"string"},"example":"CAM-RED-KOMODO"}],"responses":{"201":{"description":"Whether the customer qualifies, and what failed if not","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","example":true},"failed":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","example":"age"},"message":{"type":"string","example":"Must be 21 or older"}}}}}},"examples":{"ok":{"summary":"Customer qualifies","value":{"valid":true,"failed":[]}},"failed":{"summary":"Too young","value":{"valid":false,"failed":[{"type":"age","message":"Must be 21 or older"}]}},"noItem":{"summary":"Unknown SKU","description":"A missing item is reported through the same shape rather than as a 404.","value":{"valid":false,"failed":[{"type":"item","message":"Item not found"}]}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Rentals"],"requestBody":{"description":"The customer details to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","example":"ada@example.com"},"dateOfBirth":{"type":"string","format":"date","description":"Needed for any age restriction.","example":"1990-05-12"},"country":{"type":"string","example":"US"},"state":{"type":"string","example":"CA"}}},"example":{"email":"ada@example.com","dateOfBirth":"1990-05-12","country":"US","state":"CA"}}}}}},"/storefront/invoices/save":{"post":{"operationId":"InvoiceController_save","summary":"Create or update an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved invoice with recalculated totals","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"A single upsert for invoices: send an `sk` to update an existing one, or omit it to create a new one.\n\nTotals are **computed on the server** from the lines — subtotal, tax, discount and total are recalculated on every save, so any figures you send for them are overwritten. Send the lines, not the arithmetic.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/save (body) -> The saved invoice with recalculated totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- Totals you supply are ignored and recomputed. Read them back from the response rather than assuming yours were kept.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/send`","requestBody":{"description":"The invoice to save. Include `sk` to update.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string","description":"Existing invoice to update. Omit to create."},"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}},"examples":{"create":{"summary":"Create a new invoice","value":{"customerEmail":"ada@example.com","customerName":"Ada Lovelace","currency":"USD","dueDate":"2026-09-30T23:59:59.000Z","items":[{"sku":"DRK-COLA-330","description":"Cola 330ml","quantity":12,"price":12}]}},"update":{"summary":"Update an existing invoice","value":{"sk":"66f1a2b3c4d5e6f708192a3b","items":[{"sku":"DRK-COLA-330","description":"Cola 330ml","quantity":24,"price":12}]}}}}}}}},"/storefront/invoices/{id}":{"get":{"operationId":"InvoiceController_get","summary":"Get an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The invoice","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Fetches one invoice with its lines, totals and recorded payments.\n\n#### Signature\n\n```http\nGET /storefront/invoices/{id} (id: string) -> The invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/invoices/{id}/amount-due`"}},"/storefront/invoices":{"get":{"operationId":"InvoiceController_list","summary":"List invoices","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"]},"example":"overdue"},{"name":"search","required":false,"in":"query","schema":{"type":"string"},"description":"Free-text search across invoice number and customer.","example":"ada@example.com"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of invoices","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}},"total":{"type":"integer","example":42}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Lists invoices with optional status filtering, free-text search and paging.\n\n#### Signature\n\n```http\nGET /storefront/invoices (status?: string, search?: string, page?: integer, pageSize?: integer) -> A page of invoices\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/invoices/dashboard/metrics`"}},"/storefront/invoices/{id}/send":{"post":{"operationId":"InvoiceController_send","summary":"Send an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated invoice (status, sentDate, sentTo)","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"400":{"description":"This invoice has no line items, so there is nothing to send — No lines and nothing owed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This invoice has no line items, so there is nothing to send","path":"/storefront/invoices/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Delivers the invoice to the customer by email, SMS, or both. Recipients default to the contact details on the invoice; pass `emails`, `phones` or `recipients` to override.\n\nAt least one usable recipient must resolve, or the request is refused rather than silently sending nothing. `channels` defaults to `[\"email\"]`; `recipients` is an alias of `emails`.\n\nThe invoice becomes `sent` — except a **quote**, which stays a quote until the customer accepts it (that is when it is booked as a receivable).\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/send (id: string, body) -> The updated invoice (status, sentDate, sentTo)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- Sends every time it is called — there is no once-only guard, so re-sending mails the customer again.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `400` | NOTHING_TO_PAY | This invoice has no line items, so there is nothing to send | No lines and nothing owed. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/reminder`","requestBody":{"description":"Where to send it. All optional when the invoice carries contact details.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"channels":{"type":"array","items":{"type":"string","enum":["email","sms"]},"description":"Which channels to use. Defaults to `[\"email\"]`.","example":["email"]},"emails":{"type":"array","items":{"type":"string"},"description":"Explicit email recipients.","example":["ada@example.com"]},"phones":{"type":"array","items":{"type":"string"},"description":"Explicit SMS recipients.","example":["+15551234567"]},"recipients":{"type":"array","items":{"type":"string"},"description":"Alias of `emails`."}}},"examples":{"default":{"summary":"Use the invoice contacts","value":{}},"both":{"summary":"Email and SMS explicitly","value":{"channels":["email","sms"],"emails":["ada@example.com"],"phones":["+15551234567"]}}}}}}}},"/storefront/invoices/{id}/create-order":{"post":{"operationId":"InvoiceController_createOrder","summary":"Create an order from an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The created order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/create-order","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Invoice already converted to order <orderNumber> — The invoice already carries an `orderNumber`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Invoice already converted to order <orderNumber>","path":"/storefront/invoices/{id}/create-order","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Order <orderNumber> was created, but the invoice could not be updated to reference it. Do not convert this invoice again — link it to <orderNumber> first. — The order was written successfully but the follow-up write to the invoice failed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Order <orderNumber> was created, but the invoice could not be updated to reference it. Do not convert this invoice again — link it to <orderNumber> first.","path":"/storefront/invoices/{id}/create-order","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Invoices"],"description":"Converts a paid invoice into a paid order, so invoiced sales land in the same fulfilment pipeline as storefront orders.\n\n**Conversion happens once.** Once an invoice carries an `orderNumber`, a second attempt is refused with a `409` naming the existing order.\n\nThere is one failure mode worth planning for: if the order is created but the invoice cannot then be updated to reference it, the call returns a `500` whose message names the order number and tells you not to convert again. **Do not retry that request** — the order exists. Link the invoice to it manually by setting `orderNumber`, then continue.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/create-order (id: string) -> The created order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- This is the one endpoint here where a `500` carries actionable, non-generic instructions — read the message rather than treating it as a transient failure.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `409` | ALREADY_CONVERTED | Invoice already converted to order <orderNumber> | The invoice already carries an `orderNumber`. | The order exists — read it rather than converting again. |\n| `500` | ORDER_CREATED_LINK_FAILED | Order <orderNumber> was created, but the invoice could not be updated to reference it. Do not convert this invoice again — link it to <orderNumber> first. | The order was written successfully but the follow-up write to the invoice failed. | **Do not retry.** The order exists. Set `orderNumber` on the invoice with `POST /storefront/invoices/save`, then carry on. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /storefront/invoices/save`"}},"/storefront/invoices/{id}/mark-paid":{"post":{"operationId":"InvoiceController_markPaid","summary":"Mark an invoice as paid","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated invoice","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/mark-paid","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Records a payment against the invoice and moves it towards `paid`. Supply `amount` for a partial payment; omitting it settles the invoice in full.\n\nUse this for payments taken outside the platform — a bank transfer or a cheque. Payments made through the public payment page record themselves.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/mark-paid (id: string, body) -> The updated invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- Not idempotent — each call records another payment. Check `GET /storefront/invoices/{id}/amount-due` before retrying.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/refund`","requestBody":{"description":"How it was paid.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"method":{"type":"string","description":"How the money arrived.","example":"bank_transfer"},"ref":{"type":"string","description":"Reference for reconciliation.","example":"BACS-99182"},"amount":{"type":"number","description":"Partial amount. Omit to settle the full balance.","example":50}}},"examples":{"full":{"summary":"Settle in full","value":{"method":"bank_transfer","ref":"BACS-99182"}},"partial":{"summary":"Record a part payment","value":{"method":"bank_transfer","ref":"BACS-99182","amount":50}}}}}}}},"/storefront/invoices/{id}/accept":{"post":{"operationId":"InvoiceController_accept","summary":"Accept a quote on the customer's behalf","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk` or number.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The invoice, now `sent`","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"400":{"description":"This is not an open quote (status: <status>) / This quote has already been accepted — The document is not in `quote` status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This is not an open quote (status: <status>) / This quote has already been accepted","path":"/storefront/invoices/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"For when the customer said yes on the phone. The quote becomes a `sent` invoice, stamped `acceptedAt`/`acceptedBy` (the typed `name`), is booked as a receivable, and the customer is emailed the acceptance and the invoice (`quote-accepted`, `invoice-generated`). After this the payment page takes payment.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/accept (id: string, body) -> The invoice, now `sent`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `400` | QUOTE_NOT_OPEN | This is not an open quote (status: <status>) / This quote has already been accepted | The document is not in `quote` status. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/decline`\n- `POST /storefront/invoices/pay/{id}/accept`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Who accepted — recorded as the signature."},"note":{"type":"string","description":"Up to 2000 characters."}}},"example":{"name":"Ada Lovelace","note":"Confirmed by phone 3pm"}}}}}},"/storefront/invoices/{id}/decline":{"post":{"operationId":"InvoiceController_decline","summary":"Record that the customer declined a quote","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk` or number.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The invoice, now `declined`","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"400":{"description":"This is not an open quote (status: <status>) / This quote has already been accepted — The document is not in `quote` status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This is not an open quote (status: <status>) / This quote has already been accepted","path":"/storefront/invoices/{id}/decline","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/decline","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"The quote becomes `declined` with the reason (up to 2000 characters; \"No reason given\" when empty). The payment page then shows it as declined and takes no payment.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/decline (id: string, body) -> The invoice, now `declined`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `400` | QUOTE_NOT_OPEN | This is not an open quote (status: <status>) / This quote has already been accepted | The document is not in `quote` status. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"},"name":{"type":"string"}}},"example":{"reason":"Went with another supplier"}}}}}},"/storefront/invoices/{id}/mark-overdue":{"post":{"operationId":"InvoiceController_markOverdue","summary":"Mark an invoice as overdue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated invoice","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/mark-overdue","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Moves the invoice to `overdue`, bringing it into the overdue filter and dashboard metrics. This is a manual flag — nothing marks invoices overdue automatically on their due date.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/mark-overdue (id: string) -> The updated invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/reminder`"}},"/storefront/invoices/{id}/cancel":{"post":{"operationId":"InvoiceController_cancel","summary":"Cancel an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The cancelled invoice","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Voids an invoice, recording the reason. The record is kept so the number is not reused and the history stays auditable.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/cancel (id: string, body) -> The cancelled invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- Cancelling does not refund anything. Refund a paid invoice first, then cancel — or use `refund` with `cancel: true` to do both.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/reopen`\n- `POST /storefront/invoices/{id}/refund`","requestBody":{"description":"Why it is being cancelled.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Recorded on the invoice.","example":"Raised against the wrong customer"}}},"example":{"reason":"Raised against the wrong customer"}}}}}},"/storefront/invoices/{id}/reopen":{"post":{"operationId":"InvoiceController_reopen","summary":"Reopen an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The reopened invoice","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/reopen","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Returns a cancelled invoice to `draft` so it can be corrected and sent again. The counterpart to cancel.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/reopen (id: string) -> The reopened invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/cancel`"}},"/storefront/invoices/{id}/duplicate":{"post":{"operationId":"InvoiceController_duplicate","summary":"Duplicate an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The new draft invoice","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/duplicate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Copies an invoice into a new `draft` with a fresh number — the fast path for recurring billing, where the same lines go out to the same customer each period.\n\nPayments, status and any order link are **not** copied; the duplicate starts clean.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/duplicate (id: string) -> The new draft invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/save`"}},"/storefront/invoices/{id}/reminder":{"post":{"operationId":"InvoiceController_sendReminder","summary":"Send a payment reminder","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"origin","in":"header","required":false,"description":"Origin used to build the payment link in the reminder. Sent automatically by browsers.","schema":{"type":"string"},"example":"https://admin.example.com"}],"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"No customer email — The invoice has no `customerEmail`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No customer email","path":"/storefront/invoices/{id}/reminder","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/reminder","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Emails the customer a reminder that the invoice is outstanding, with a link back to the public payment page. Requires an email on the invoice.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/reminder (id: string) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `400` | NO_CUSTOMER_EMAIL | No customer email | The invoice has no `customerEmail`. | Reminders are email-only. Add an email to the invoice, or use `send` with an explicit phone for SMS. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/send`"}},"/storefront/invoices/dashboard/metrics":{"get":{"operationId":"InvoiceController_getMetrics","summary":"Get invoice dashboard metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Aggregate invoice metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Aggregate invoice figures for the org — outstanding, overdue and collected. The read behind an invoicing dashboard.\n\n#### Signature\n\n```http\nGET /storefront/invoices/dashboard/metrics () -> Aggregate invoice metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- Declared before `GET /storefront/invoices/{id}`, so `dashboard` resolves as this route rather than as an invoice id.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/invoices`"}},"/storefront/invoices/{id}/refund":{"post":{"operationId":"InvoiceController_refund","summary":"Refund an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The refund result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot refund invoice with status '<status>'. Invoice must be paid first. — The invoice has not been paid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot refund invoice with status '<status>'. Invoice must be paid first.","path":"/storefront/invoices/{id}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Refund failed: <gateway message> — The payment gateway rejected the refund.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"Refund failed: <gateway message>","path":"/storefront/invoices/{id}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Storefront · Invoices"],"description":"Refunds a paid invoice in part or in full, optionally cancelling it in the same call.\n\nThe refund goes back through the **original payment gateway** when the invoice was paid that way. An invoice settled by manual payments alone is refunded as a record only, with no gateway involved.\n\nOmitting `amount` refunds the maximum still refundable — total paid minus anything already returned. A requested amount above that ceiling is rejected, and the error reports the paid and refunded figures so you can see how it was derived.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/{id}/refund (id: string, body) -> The refund result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- This is the only endpoint in the storefront that returns a `502` — it distinguishes a gateway refusal from a platform failure.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `400` | NOT_PAID | Cannot refund invoice with status '<status>'. Invoice must be paid first. | The invoice has not been paid. | Cancel an unpaid invoice instead — there is nothing to return. |\n| `502` | GATEWAY_REFUND_FAILED | Refund failed: <gateway message> | The payment gateway rejected the refund. | A `502` means the gateway was reached and refused. Resolve it with the provider; the invoice is unchanged. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/{id}/mark-paid`\n- `POST /storefront/invoices/{id}/cancel`","requestBody":{"description":"How much to refund, and whether to cancel.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Partial amount. Omit to refund everything still refundable.","example":50},"reason":{"type":"string","description":"Recorded with the refund.","example":"Goods returned"},"cancel":{"type":"boolean","default":false,"description":"Also cancel the invoice after refunding.","example":false}}},"examples":{"full":{"summary":"Refund everything","value":{"reason":"Goods returned"}},"partial":{"summary":"Partial refund","value":{"amount":50,"reason":"Two lines returned"}},"fullAndCancel":{"summary":"Refund and cancel","value":{"cancel":true,"reason":"Order voided"}}}}}}}},"/storefront/invoices/{id}/amount-due":{"get":{"operationId":"InvoiceController_getAmountDue","summary":"Get the amount still due","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk` **or** invoice number — both resolve here.","schema":{"type":"string"},"example":"INV-4821"}],"responses":{"200":{"description":"The outstanding balance derived from transactions","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"Computes what is outstanding **from the actual transaction records**, rather than trusting the `amountPaid` field on the invoice.\n\nUse this rather than subtracting `amountPaid` from `total` yourself — it is the figure that reflects the ledger, including payments recorded through the public payment page and any refunds.\n\n#### Signature\n\n```http\nGET /storefront/invoices/{id}/amount-due (id: string) -> The outstanding balance derived from transactions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- This route accepts an invoice number as well as an `sk`, which most of the others do not.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/invoices/{id}`"}},"/storefront/invoices/{id}/payment-link":{"get":{"operationId":"InvoiceController_getPaymentLink","summary":"Get the customer payment link for an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Invoice `sk` or number.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"{ url, reason? }","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","nullable":true,"example":"https://shop.example.com/pay?invoiceNumber=INV-4821"},"reason":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/{id}/payment-link","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"The URL where the customer pays this invoice: the site's Payment page when the operator runs one, otherwise the hosted page, with `invoiceNumber` on the query string. `url` is null — with a `reason` — when there is no page to send the customer to.\n\n#### Signature\n\n```http\nGET /storefront/invoices/{id}/payment-link (id: string) -> { url, reason? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/storefront/invoices/pay/{id}":{"get":{"operationId":"InvoiceController_getForPayment","summary":"Get an invoice for the payment page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Payment options and the amount due","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the page may take payment now."},"reason":{"type":"string","description":"Why not, when `enabled` is false."},"gateways":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"Public gateway view: sk, name, configured, data { provider, name, publishableKey / clientId, sandbox, testMode, instructions, … }, and for Stripe a payIntent { id, client_secret, publishableKey }."}},"invoice":{"type":"object","additionalProperties":true,"description":"sk, number, total, currency, status, customerName, customerEmail, products, subTotal, tax, discount, invoiceDate, dueDate, paymentDate, remarks, validUntil, acceptedAt/By, declinedAt, declineReason."},"quote":{"type":"object","nullable":true,"description":"Null when the document was never a quote.","properties":{"open":{"type":"boolean","description":"Still waiting for the customer."},"validUntil":{"type":"string","nullable":true},"expired":{"type":"boolean"},"declined":{"type":"boolean"},"declinedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"acceptedAt":{"type":"string","format":"date-time"},"acceptedBy":{"type":"string","description":"The typed name."},"note":{"type":"string","nullable":true}}}}}}}},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/pay/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"The **public** read behind a customer payment link. Returns what the payment page needs — the amount due and the gateways available — without requiring the customer to sign in.\n\nThe invoice `sk` is the only credential, so treat payment links as secrets and do not make ids guessable.\n\n#### Signature\n\n```http\nGET /storefront/invoices/pay/{id} (id: string) -> Payment options and the amount due\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated. Anyone holding the link can read the invoice.\n- `enabled` is false — with no gateways — while the document is an open quote (`reason: \"This is a quote — accept it first\"`), after it was declined, or when online payment is switched off on the invoice. `quote` says where a quote stands.\n- Each gateway carries only its public checkout fields (provider, name, publishable key / client id, sandbox or test mode, instructions) and `configured`; credentials never leave the server.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /storefront/invoices/pay/{id}`\n- `POST /storefront/invoices/pay/{id}/accept`\n- `POST /storefront/invoices/pay/{id}/decline`"},"post":{"operationId":"InvoiceController_recordPayment","summary":"Record a payment from the payment page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated invoice","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/pay/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"The **public** write that records a customer payment made through the payment page, after the gateway has confirmed it.\n\nIt records what it is told: the caller supplies the gateway, reference and amount. Since the route is unauthenticated, verify the payment with the provider — `GET /storefront/verify-payment/...` — before trusting a client-reported amount.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/pay/{id} (id: string, body) -> The updated invoice\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated and unverified — the amount is taken on trust. Confirm with the provider before relying on it.\n- Not idempotent: a retry records a second payment against the invoice.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /storefront/invoices/pay/{id}`\n- `GET /storefront/verify-payment/{provider}/{configId}/{paymentId}`","requestBody":{"description":"The payment the gateway confirmed.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","ref","amount"],"properties":{"gateway":{"type":"string","description":"Which gateway took the payment.","example":"stripe"},"ref":{"type":"string","description":"Gateway reference.","example":"pi_3PabcXYZ"},"amount":{"type":"number","description":"Amount paid.","example":144},"method":{"type":"string","description":"Payment method used.","example":"card"}}},"example":{"gateway":"stripe","ref":"pi_3PabcXYZ","amount":144,"method":"card"}}}}}},"/storefront/invoices/pay/{id}/accept":{"post":{"operationId":"InvoiceController_acceptQuote","summary":"Accept a quote from the payment page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk` or number.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The invoice, now `sent`","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"400":{"description":"This is not an open quote (status: <status>) / This quote has already been accepted — The document is not in `quote` status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This is not an open quote (status: <status>) / This quote has already been accepted","path":"/storefront/invoices/pay/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/pay/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"The **public** accept step on the payment page: the customer types their name to agree. Same effect as the staff accept — the quote becomes a payable `sent` invoice, is booked, and the acceptance and invoice are emailed.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/pay/{id}/accept (id: string, body) -> The invoice, now `sent`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated: anyone with the link can accept. The id is the only credential.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `400` | QUOTE_NOT_OPEN | This is not an open quote (status: <status>) / This quote has already been accepted | The document is not in `quote` status. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"note":{"type":"string"}}},"example":{"name":"Ada Lovelace"}}}}}},"/storefront/invoices/pay/{id}/decline":{"post":{"operationId":"InvoiceController_declineQuote","summary":"Decline a quote from the payment page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Invoice `sk` or number.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The invoice, now `declined`","content":{"application/json":{"schema":{"type":"object","description":"An invoice (`sf_invoice`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human invoice number.","example":"INV-4821"},"status":{"type":"string","enum":["new","draft","quote","declined","sent","paid","paid-partial","overpaid","overdue","refunded","cancelled"],"example":"sent"},"customerEmail":{"type":"string","description":"Required before the invoice can be sent or reminded.","example":"ada@example.com"},"customerPhone":{"type":"string","example":"+15551234567"},"customerName":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"DRK-COLA-330"},"description":{"type":"string","example":"Cola 330ml"},"quantity":{"type":"number","example":12},"price":{"type":"number","description":"Unit price.","example":12},"amount":{"type":"number","description":"Line total. Recalculated on save.","example":144},"taxRate":{"type":"number","example":0}}}},"subtotal":{"type":"number","description":"Computed on save from the lines.","example":144},"tax":{"type":"number","example":0},"discount":{"type":"number","example":0},"total":{"type":"number","description":"Computed on save.","example":144},"amountPaid":{"type":"number","example":0},"currency":{"type":"string","example":"USD"},"dueDate":{"type":"string","format":"date-time","example":"2026-09-30T23:59:59.000Z"},"payments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Recorded tenders."},"orderNumber":{"type":"string","description":"Set once the invoice has been converted to an order. Its presence blocks a second conversion.","example":"A7K2M9QX4"}}}}}}}},"400":{"description":"This is not an open quote (status: <status>) / This quote has already been accepted — The document is not in `quote` status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This is not an open quote (status: <status>) / This quote has already been accepted","path":"/storefront/invoices/pay/{id}/decline","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Invoice not found — No invoice in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invoice not found","path":"/storefront/invoices/pay/{id}/decline","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Storefront · Invoices"],"description":"The **public** decline step: the quote becomes `declined` with the customer's reason.\n\n#### Signature\n\n```http\nPOST /storefront/invoices/pay/{id}/decline (id: string, body) -> The invoice, now `declined`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated: anyone with the link can decline.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVOICE_NOT_FOUND | Invoice not found | No invoice in the org has that id. | Check the id with `GET /storefront/invoices`. Note this service does return a real `404`, unlike the discount and gift card services. |\n| `400` | QUOTE_NOT_OPEN | This is not an open quote (status: <status>) / This quote has already been accepted | The document is not in `quote` status. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"},"name":{"type":"string"}}}}}}}},"/sales-channel/channels":{"get":{"operationId":"SalesChannelController_getAvailableChannels","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Channels keyed by id","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List supported channels","description":"Every channel the platform can sell through, with the capabilities each one supports — order sync, pricing sync, inventory sync. Static platform capability, not org configuration; nothing here means the org can actually sell on it.\n\n#### Signature\n\n```http\nGET /sales-channel/channels () -> Channels keyed by id\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/channels/configured`","tags":["Sales channels"]}},"/sales-channel/channels/configured":{"get":{"operationId":"SalesChannelController_getConfiguredChannels","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Channels with an `enabled` flag","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"channel":{"type":"string","example":"amazon"},"config":{"type":"object","additionalProperties":true},"enabled":{"type":"boolean","example":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List configured channels","description":"The same channel list, each flagged with whether **this org** has working integration credentials. This is the one to read before offering a channel in a UI.\n\n#### Signature\n\n```http\nGET /sales-channel/channels/configured () -> Channels with an `enabled` flag\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/channels`","tags":["Sales channels"]}},"/sales-channel/channels/status":{"get":{"operationId":"SalesChannelController_getAllChannelStatuses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Per-channel status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get all channel statuses","description":"Live connection status for every configured channel — whether credentials still authenticate and when each last synced. Use it for a health board; a channel can be enabled but broken.\n\n#### Signature\n\n```http\nGET /sales-channel/channels/status () -> Per-channel status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/channels/{channelId}/status`","tags":["Sales channels"]}},"/sales-channel/channels/{channelId}/status":{"get":{"operationId":"SalesChannelController_getChannelStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"200":{"description":"The channel status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/channels/{channelId}/status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get one channel's status","description":"Connection status and last-sync detail for a single channel.\n\n#### Signature\n\n```http\nGET /sales-channel/channels/{channelId}/status (channelId: string) -> The channel status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/channels/status`","tags":["Sales channels"]}},"/sales-channel/channels/{channelId}/enable":{"post":{"operationId":"SalesChannelController_enableChannel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The enable result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/channels/{channelId}/enable","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Enable a channel","description":"Stores the org's credentials for a channel and turns it on. The body shape is per-channel — a marketplace seller id and API keys, an OAuth token — so read `GET /sales-channel/channels` for what the channel expects.\n\nEnabling does not push anything: listings, inventory and prices still have to be synced explicitly.\n\n#### Signature\n\n```http\nPOST /sales-channel/channels/{channelId}/enable (channelId: string, body) -> The enable result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Stores credentials — do not log the request body.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/channels/{channelId}/disable`","tags":["Sales channels"],"requestBody":{"description":"Channel credentials and options.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sellerId":"A1B2C3D4","apiKey":"<key>","marketplace":"US"}}}}}},"/sales-channel/channels/{channelId}/disable":{"post":{"operationId":"SalesChannelController_disableChannel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The disable result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/channels/{channelId}/disable","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Disable a channel","description":"Turns a channel off so nothing further syncs to it. Listings already live on the marketplace are **not** taken down — they stop receiving inventory and price updates, which leaves them selling at the last synced values. End listings on the channel itself if that is what you want.\n\n#### Signature\n\n```http\nPOST /sales-channel/channels/{channelId}/disable (channelId: string) -> The disable result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Live listings stay up and stop being updated.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/channels/{channelId}/enable`","tags":["Sales channels"]}},"/sales-channel/sync/history":{"get":{"operationId":"SalesChannelController_getSyncHistory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"},{"name":"operation","required":false,"in":"query","schema":{"type":"string"},"description":"Operation name, e.g. `inventory-sync`.","example":"inventory-sync"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Sync history entries, newest first","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get sync history","description":"The log of sync operations across channels — what ran, when, and whether it succeeded. The first place to look when a channel's stock or prices are stale.\n\n#### Signature\n\n```http\nGET /sales-channel/sync/history (channelId?: string, operation?: string, limit?: integer) -> Sync history entries, newest first\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/inventory/sync`","tags":["Sales channels"]}},"/sales-channel/channels/{channelId}/execute":{"post":{"operationId":"SalesChannelController_executeChannelOperation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The channel's response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/channels/{channelId}/execute","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Execute a channel operation","description":"Escape hatch: runs a named operation against one channel, passing `data` through to the channel adapter. Which operations exist depends on the channel — this is the route to use when a capability has no dedicated endpoint.\n\nBecause it is a passthrough, the payload is not validated here; the channel decides what is acceptable.\n\n#### Signature\n\n```http\nPOST /sales-channel/channels/{channelId}/execute (channelId: string, body) -> The channel's response\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Unvalidated passthrough — the channel accepts or rejects the payload.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/channels/bulk-execute`","tags":["Sales channels"],"requestBody":{"description":"The operation and its payload.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["operation","data"],"properties":{"operation":{"type":"string","example":"update-listing"},"data":{"type":"object","additionalProperties":true}}},"example":{"operation":"update-listing","data":{"sku":"COLA-330","title":"Cola 330ml"}}}}}}},"/sales-channel/channels/bulk-execute":{"post":{"operationId":"SalesChannelController_executeBulkOperation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Per-channel results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Execute an operation on many channels","description":"Runs the same operation against several channels in one call. Each channel is attempted independently, so a partial failure is normal — read the per-channel results rather than assuming a 2xx means everything succeeded.\n\n#### Signature\n\n```http\nPOST /sales-channel/channels/bulk-execute (body) -> Per-channel results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Partial success is expected — inspect each channel's result.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/channels/{channelId}/execute`","tags":["Sales channels"],"requestBody":{"description":"Channels, operation and payload.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["channelIds","operation","data"],"properties":{"channelIds":{"type":"array","items":{"type":"string"},"example":["amazon","ebay"]},"operation":{"type":"string","example":"update-listing"},"data":{"type":"object","additionalProperties":true}}},"example":{"channelIds":["amazon","ebay"],"operation":"update-listing","data":{"sku":"COLA-330"}}}}}}},"/sales-channel/inventory/{sku}":{"get":{"operationId":"InventorySyncController_getMasterInventory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"COLA-330"}],"responses":{"200":{"description":"Master inventory","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get master inventory for a SKU","description":"The authoritative stock figure for a SKU — the single number every channel is synced from.\n\n#### Signature\n\n```http\nGET /sales-channel/inventory/{sku} (sku: string) -> Master inventory\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/inventory/{sku}/channels`","tags":["Sales channels"]},"put":{"operationId":"InventorySyncController_updateMasterInventory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"COLA-330"}],"responses":{"200":{"description":"The updated inventory","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Product <sku> not found — No product carries that SKU. Returned as 400, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Product <sku> not found","path":"/sales-channel/inventory/{sku}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update master inventory","description":"Sets the master stock level for a SKU. `syncToChannels` decides whether the new figure is pushed to the marketplaces immediately — leave it off and the channels keep selling against the old number until a sync runs.\n\n#### Signature\n\n```http\nPUT /sales-channel/inventory/{sku} (sku: string, body) -> The updated inventory\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Absolute set, not a delta.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PRODUCT_NOT_FOUND | Product <sku> not found | No product carries that SKU. Returned as 400, not 404. | Check the SKU against the catalogue. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /sales-channel/inventory/bulk/update`","tags":["Sales channels"],"requestBody":{"description":"The new quantity.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["quantity"],"properties":{"quantity":{"type":"number","example":120},"syncToChannels":{"type":"boolean","description":"Push to channels immediately.","example":true},"reason":{"type":"string","example":"Stock count"}}},"example":{"quantity":120,"syncToChannels":true,"reason":"Stock count"}}}}}},"/sales-channel/inventory/bulk/update":{"put":{"operationId":"InventorySyncController_bulkUpdateInventory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Per-SKU results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Bulk-update master inventory","description":"Sets stock for many SKUs at once — the endpoint a stock count or a warehouse feed uses. Each quantity is absolute, not a delta.\n\n#### Signature\n\n```http\nPUT /sales-channel/inventory/bulk/update (body) -> Per-SKU results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Quantities are absolute.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /sales-channel/inventory/{sku}`","tags":["Sales channels"],"requestBody":{"description":"The updates.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["updates"],"properties":{"updates":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"COLA-330"},"quantity":{"type":"number","example":120}}}},"syncToChannels":{"type":"boolean","example":true}}},"example":{"updates":[{"sku":"COLA-330","quantity":120},{"sku":"COLA-500","quantity":40}],"syncToChannels":true}}}}}},"/sales-channel/inventory/reserve":{"post":{"operationId":"InventorySyncController_reserveInventory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The reservation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Product <sku> not found — No product carries that SKU. Returned as 400, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Product <sku> not found","path":"/sales-channel/inventory/reserve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Reserve inventory for an order","description":"Holds stock for an order so it cannot be sold twice while the order is being paid for or fulfilled. Reservations are the mechanism that prevents oversells across channels.\n\nEvery reservation must end in a `commit` or a `release` — one left open holds stock indefinitely.\n\n#### Signature\n\n```http\nPOST /sales-channel/inventory/reserve (body) -> The reservation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Always follow with commit or release.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PRODUCT_NOT_FOUND | Product <sku> not found | No product carries that SKU. Returned as 400, not 404. | Check the SKU against the catalogue. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/inventory/reservation/{reservationId}/commit`","tags":["Sales channels"],"requestBody":{"description":"What to reserve and for which order.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["items","orderId"],"properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string","example":"COLA-330"},"quantity":{"type":"number","example":2}}}},"orderId":{"type":"string","example":"ORD-4821"}}},"example":{"items":[{"sku":"COLA-330","quantity":2}],"orderId":"ORD-4821"}}}}}},"/sales-channel/inventory/reservation/{reservationId}/release":{"post":{"operationId":"InventorySyncController_releaseReservation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"reservationId","required":true,"in":"path","schema":{"type":"string"},"description":"Reservation id.","example":"RSV-4821"}],"responses":{"201":{"description":"The release result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Reservation <id> not found — No reservation has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Reservation <id> not found","path":"/sales-channel/inventory/reservation/{reservationId}/release","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Release a reservation","description":"Returns held stock to available — the order was cancelled or the payment failed. Run this on abandoned checkouts; unreleased holds slowly starve the sellable quantity.\n\n#### Signature\n\n```http\nPOST /sales-channel/inventory/reservation/{reservationId}/release (reservationId: string) -> The release result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RESERVATION_NOT_FOUND | Reservation <id> not found | No reservation has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/inventory/reserve`","tags":["Sales channels"]}},"/sales-channel/inventory/reservation/{reservationId}/commit":{"post":{"operationId":"InventorySyncController_commitReservation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"reservationId","required":true,"in":"path","schema":{"type":"string"},"description":"Reservation id.","example":"RSV-4821"}],"responses":{"201":{"description":"The commit result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Reservation <id> not found — No reservation has that id. Returned as 400, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Reservation <id> not found","path":"/sales-channel/inventory/reservation/{reservationId}/commit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Commit a reservation","description":"Converts a hold into an actual stock decrement — the order shipped or was paid. Irreversible through this API: undoing it means putting the stock back with an inventory update.\n\n#### Signature\n\n```http\nPOST /sales-channel/inventory/reservation/{reservationId}/commit (reservationId: string) -> The commit result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Permanently decrements stock.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RESERVATION_NOT_FOUND | Reservation <id> not found | No reservation has that id. Returned as 400, not 404. | Check the id from the reserve response. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/inventory/reservation/{reservationId}/release`","tags":["Sales channels"]}},"/sales-channel/inventory/sync/{channelId}":{"post":{"operationId":"InventorySyncController_syncToChannel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The sync result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/inventory/sync/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Push inventory to one channel","description":"Pushes the given SKUs' stock levels to a single channel.\n\n#### Signature\n\n```http\nPOST /sales-channel/inventory/sync/{channelId} (channelId: string, body) -> The sync result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/inventory/sync`","tags":["Sales channels"],"requestBody":{"description":"The items to push.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}},"example":{"items":[{"sku":"COLA-330","quantity":120}]}}}}}},"/sales-channel/inventory/sync":{"post":{"operationId":"InventorySyncController_syncToAllChannels","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Per-channel results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Push inventory to all channels","description":"Pushes the given SKUs' stock to every configured channel. Channels are attempted independently — check the per-channel results, since a single failure leaves that marketplace stale and able to oversell.\n\n#### Signature\n\n```http\nPOST /sales-channel/inventory/sync (body) -> Per-channel results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Partial failure leaves that channel stale.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/sync/history`","tags":["Sales channels"],"requestBody":{"description":"The items to push.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","properties":{"sku":{"type":"string"},"quantity":{"type":"number"}}}}}},"example":{"items":[{"sku":"COLA-330","quantity":120}]}}}}}},"/sales-channel/inventory/pull/{channelId}":{"post":{"operationId":"InventorySyncController_pullFromChannel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"What the channel reports","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/inventory/pull/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pull inventory from a channel","description":"Reads stock levels back from a channel — for reconciling after a marketplace-side change, or for a first import. Pulling does not overwrite master; compare before deciding which side is right.\n\n#### Signature\n\n```http\nPOST /sales-channel/inventory/pull/{channelId} (channelId: string) -> What the channel reports\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/inventory/{sku}/channels`","tags":["Sales channels"]}},"/sales-channel/inventory/alerts/low-stock":{"get":{"operationId":"InventorySyncController_getLowStockItems","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Low-stock items","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List low-stock items","description":"SKUs at or below their reorder threshold — what to restock, or to pull from channels before it oversells.\n\n#### Signature\n\n```http\nGET /sales-channel/inventory/alerts/low-stock () -> Low-stock items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /sales-channel/inventory/bulk/update`","tags":["Sales channels"]}},"/sales-channel/inventory/{sku}/channels":{"get":{"operationId":"InventorySyncController_getInventoryAcrossChannels","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"COLA-330"}],"responses":{"200":{"description":"Per-channel quantities","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a SKU's stock across channels","description":"What each channel currently believes the stock is, next to the master figure. Divergence here is what causes oversells — a channel still showing stock that master no longer has.\n\n#### Signature\n\n```http\nGET /sales-channel/inventory/{sku}/channels (sku: string) -> Per-channel quantities\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/inventory/sync`","tags":["Sales channels"]}},"/sales-channel/mappings/categories":{"get":{"operationId":"ChannelMappingController_getAllCategoryMappings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Category mappings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List category mappings","description":"How the org's own categories translate to each channel's taxonomy. Marketplaces reject listings in the wrong category, so these mappings are what make a listing publishable.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/categories () -> Category mappings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/categories`","tags":["Sales channels"]},"post":{"operationId":"ChannelMappingController_saveCategoryMapping","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved mapping","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Save a category mapping","description":"Creates or replaces a category mapping. Saving by source category, so re-posting the same source category overwrites the previous mapping rather than adding a second.\n\n#### Signature\n\n```http\nPOST /sales-channel/mappings/categories (body) -> The saved mapping\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /sales-channel/mappings/categories/{mappingId}`","tags":["Sales channels"],"requestBody":{"description":"The mapping.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sourceCategory":"beverages","channelId":"amazon","channelCategory":"16310101"}}}}}},"/sales-channel/mappings/categories/{sourceCategory}":{"get":{"operationId":"ChannelMappingController_getCategoryMapping","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sourceCategory","required":true,"in":"path","schema":{"type":"string"},"description":"The org's own category.","example":"beverages"}],"responses":{"200":{"description":"The mapping","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a category mapping","description":"The mapping for one source category across all channels.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/categories/{sourceCategory} (sourceCategory: string) -> The mapping\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/mappings/categories/{sourceCategory}/channel/{channelId}`","tags":["Sales channels"]}},"/sales-channel/mappings/categories/{mappingId}":{"delete":{"operationId":"ChannelMappingController_deleteCategoryMapping","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"mappingId","required":true,"in":"path","schema":{"type":"string"},"description":"Mapping id.","example":"MAP-4821"}],"responses":{"200":{"description":"The delete result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a category mapping","description":"Removes a category mapping. Products in that category can no longer be transformed for the affected channel until a new mapping exists.\n\n#### Signature\n\n```http\nDELETE /sales-channel/mappings/categories/{mappingId} (mappingId: string) -> The delete result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/categories`","tags":["Sales channels"]}},"/sales-channel/mappings/categories/{sourceCategory}/channel/{channelId}":{"get":{"operationId":"ChannelMappingController_getChannelCategory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sourceCategory","required":true,"in":"path","schema":{"type":"string"},"description":"The org's own category.","example":"beverages"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"200":{"description":"The channel category","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Resolve a category for a channel","description":"Resolves one source category to the specific category id that channel expects — the single lookup a listing builder needs.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/categories/{sourceCategory}/channel/{channelId} (sourceCategory: string, channelId: string) -> The channel category\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/transform/{channelId}`","tags":["Sales channels"]}},"/sales-channel/mappings/attributes":{"get":{"operationId":"ChannelMappingController_getAllAttributeMappings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Attribute mappings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List attribute mappings","description":"How the org's product fields map onto each channel's attribute names — `colour` to `color_name`, and so on.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/attributes () -> Attribute mappings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/attributes`","tags":["Sales channels"]},"post":{"operationId":"ChannelMappingController_saveAttributeMapping","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved mapping","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Save an attribute mapping","description":"Creates or replaces an attribute mapping. A mapping can be marked required — a product missing that source field then fails validation rather than publishing an incomplete listing.\n\n#### Signature\n\n```http\nPOST /sales-channel/mappings/attributes (body) -> The saved mapping\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/validate/{channelId}`","tags":["Sales channels"],"requestBody":{"description":"The mapping.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sourceField":"colour","channelId":"amazon","targetField":"color_name","required":true}}}}}},"/sales-channel/mappings/attributes/{sourceAttribute}":{"get":{"operationId":"ChannelMappingController_getAttributeMapping","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sourceAttribute","required":true,"in":"path","schema":{"type":"string"},"description":"The org's own field name.","example":"colour"}],"responses":{"200":{"description":"The mapping","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an attribute mapping","description":"The mapping for one source attribute.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/attributes/{sourceAttribute} (sourceAttribute: string) -> The mapping\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/mappings/attributes`","tags":["Sales channels"]}},"/sales-channel/mappings/attributes/{mappingId}":{"delete":{"operationId":"ChannelMappingController_deleteAttributeMapping","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"mappingId","required":true,"in":"path","schema":{"type":"string"},"description":"Mapping id.","example":"MAP-77"}],"responses":{"200":{"description":"The delete result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete an attribute mapping","description":"Removes an attribute mapping. Transformed listings will no longer carry that field.\n\n#### Signature\n\n```http\nDELETE /sales-channel/mappings/attributes/{mappingId} (mappingId: string) -> The delete result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/attributes`","tags":["Sales channels"]}},"/sales-channel/mappings/templates":{"get":{"operationId":"ChannelMappingController_getChannelTemplates","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"}],"responses":{"200":{"description":"Templates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List channel templates","description":"Listing templates — reusable sets of defaults and field rules applied when building a listing for a channel.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/templates (channelId?: string) -> Templates\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/apply-template/{templateId}`","tags":["Sales channels"]},"post":{"operationId":"ChannelMappingController_saveChannelTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Save a channel template","description":"Creates or replaces a listing template.\n\n#### Signature\n\n```http\nPOST /sales-channel/mappings/templates (body) -> The saved template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/mappings/templates`","tags":["Sales channels"],"requestBody":{"description":"The template.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Beverages — Amazon","channelId":"amazon","defaults":{"brand":"Acme"}}}}}}},"/sales-channel/mappings/templates/{templateId}":{"get":{"operationId":"ChannelMappingController_getChannelTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"templateId","required":true,"in":"path","schema":{"type":"string"},"description":"Template id.","example":"TPL-12"}],"responses":{"200":{"description":"The template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Template <templateId> not found — No template has that id. Returned as 400, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Template <templateId> not found","path":"/sales-channel/mappings/templates/{templateId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a channel template","description":"Fetches one listing template.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/templates/{templateId} (templateId: string) -> The template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TEMPLATE_NOT_FOUND | Template <templateId> not found | No template has that id. Returned as 400, not 404. | List templates first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/templates`","tags":["Sales channels"]},"delete":{"operationId":"ChannelMappingController_deleteChannelTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"templateId","required":true,"in":"path","schema":{"type":"string"},"description":"Template id.","example":"TPL-12"}],"responses":{"200":{"description":"The delete result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a channel template","description":"Removes a listing template. Listings already published are unaffected; future builds lose its defaults.\n\n#### Signature\n\n```http\nDELETE /sales-channel/mappings/templates/{templateId} (templateId: string) -> The delete result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/templates`","tags":["Sales channels"]}},"/sales-channel/mappings/transform/{channelId}":{"post":{"operationId":"ChannelMappingController_transformProduct","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The channel-shaped product","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/mappings/transform/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Transform a product for a channel","description":"Runs a product through the category and attribute mappings and returns the channel-shaped payload — exactly what would be sent to the marketplace. Read-only preview: nothing is published.\n\n#### Signature\n\n```http\nPOST /sales-channel/mappings/transform/{channelId} (channelId: string, body) -> The channel-shaped product\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/validate/{channelId}`","tags":["Sales channels"],"requestBody":{"description":"The product to transform.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sku":"COLA-330","title":"Cola 330ml","category":"beverages","colour":"red"}}}}}},"/sales-channel/mappings/apply-template/{templateId}":{"post":{"operationId":"ChannelMappingController_applyTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"templateId","required":true,"in":"path","schema":{"type":"string"},"description":"Template id.","example":"TPL-12"}],"responses":{"201":{"description":"The product with template defaults applied","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Apply a template to a product","description":"Merges a template's defaults into a product and returns the result. A preview — the product itself is not modified.\n\n#### Signature\n\n```http\nPOST /sales-channel/mappings/apply-template/{templateId} (templateId: string, body) -> The product with template defaults applied\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/transform/{channelId}`","tags":["Sales channels"],"requestBody":{"description":"The product.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sku":"COLA-330","title":"Cola 330ml"}}}}}},"/sales-channel/mappings/validate/{channelId}":{"post":{"operationId":"ChannelMappingController_validateProduct","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"Validation result with any problems found","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/mappings/validate/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Validate a product for a channel","description":"Checks one product against a channel's requirements and reports what would be rejected — the cheap check to run before publishing, since marketplace rejections are slow and opaque.\n\n#### Signature\n\n```http\nPOST /sales-channel/mappings/validate/{channelId} (channelId: string, body) -> Validation result with any problems found\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/mappings/validate/{channelId}/bulk`","tags":["Sales channels"],"requestBody":{"description":"The product to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sku":"COLA-330","title":"Cola 330ml","category":"beverages"}}}}}},"/sales-channel/mappings/validate/{channelId}/bulk":{"get":{"operationId":"ChannelMappingController_bulkValidate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"200":{"description":"Per-product validation results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/mappings/validate/{channelId}/bulk","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Validate the whole catalogue for a channel","description":"Validates every product against one channel and reports the failures — the pre-flight before a first publish to a new marketplace.\n\nNote this is a **GET** that scans the full catalogue, so it can be slow on a large one and is not cheap to poll.\n\n#### Signature\n\n```http\nGET /sales-channel/mappings/validate/{channelId}/bulk (channelId: string) -> Per-product validation results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scans the entire catalogue — expect a long response time.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/mappings/validate/{channelId}`","tags":["Sales channels"]}},"/sales-channel/orders/sync/{channelId}":{"post":{"operationId":"OrderAggregationController_syncChannel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The sync result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/orders/sync/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pull orders from a channel","description":"Fetches orders from one marketplace into the aggregated order list. Without a date range the channel's own default window applies, so pass `fromDate` when back-filling.\n\n#### Signature\n\n```http\nPOST /sales-channel/orders/sync/{channelId} (channelId: string, body) -> The sync result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/orders/sync`","tags":["Sales channels"],"requestBody":{"description":"Optional window and status filter.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"fromDate":{"type":"string","example":"2026-08-01"},"toDate":{"type":"string","example":"2026-08-31"},"status":{"type":"string","example":"unshipped"}}},"example":{"fromDate":"2026-08-01"}}}}}},"/sales-channel/orders/sync":{"post":{"operationId":"OrderAggregationController_syncAll","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Per-channel results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pull orders from all channels","description":"Runs the order pull across every configured channel. Channels that do not support order sync are skipped rather than failing the call — read the per-channel results.\n\n#### Signature\n\n```http\nPOST /sales-channel/orders/sync (body) -> Per-channel results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/orders`","tags":["Sales channels"],"requestBody":{"description":"Optional window.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"fromDate":{"type":"string","example":"2026-08-01"},"toDate":{"type":"string","example":"2026-08-31"}}},"example":{"fromDate":"2026-08-01"}}}}}},"/sales-channel/orders":{"get":{"operationId":"OrderAggregationController_getOrders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"unshipped"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-31"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Aggregated orders","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List aggregated orders","description":"Orders from every channel in one list with a common shape — the single queue an operations team works from instead of logging into each marketplace.\n\n#### Signature\n\n```http\nGET /sales-channel/orders (channel?: string, status?: string, fromDate?: string, toDate?: string, page?: integer, pageSize?: integer) -> Aggregated orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/orders/{orderId}`","tags":["Sales channels"]}},"/sales-channel/orders/{orderId}":{"get":{"operationId":"OrderAggregationController_getOrder","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderId","required":true,"in":"path","schema":{"type":"string"},"description":"Aggregated order id.","example":"ORD-4821"}],"responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Order <orderId> not found — No aggregated order has that id. Returned as 400, not 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Order <orderId> not found","path":"/sales-channel/orders/{orderId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an aggregated order","description":"One order with its items, buyer detail and channel of origin.\n\n#### Signature\n\n```http\nGET /sales-channel/orders/{orderId} (orderId: string) -> The order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORDER_NOT_FOUND | Order <orderId> not found | No aggregated order has that id. Returned as 400, not 404. | Check the id from the order list. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /sales-channel/orders/{orderId}/status`","tags":["Sales channels"]}},"/sales-channel/orders/{orderId}/status":{"put":{"operationId":"OrderAggregationController_updateStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orderId","required":true,"in":"path","schema":{"type":"string"},"description":"Aggregated order id.","example":"ORD-4821"}],"responses":{"200":{"description":"The updated order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Order <orderId> not found — No aggregated order has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Order <orderId> not found","path":"/sales-channel/orders/{orderId}/status","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update an order's status","description":"Moves an aggregated order to a new status, optionally attaching a tracking number. Marking an order shipped with tracking is what pushes fulfilment back to the marketplace — the buyer sees it, so the tracking number must be real.\n\n#### Signature\n\n```http\nPUT /sales-channel/orders/{orderId}/status (orderId: string, body) -> The updated order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Shipment status is visible to the marketplace buyer.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORDER_NOT_FOUND | Order <orderId> not found | No aggregated order has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/orders`","tags":["Sales channels"],"requestBody":{"description":"The new status.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","example":"shipped"},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"}}},"example":{"status":"shipped","trackingNumber":"1Z999AA10123456784"}}}}}},"/sales-channel/orders/stats/summary":{"get":{"operationId":"OrderAggregationController_getStats","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"Order statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get order statistics","description":"Order counts and value by channel over a date range.\n\n#### Signature\n\n```http\nGET /sales-channel/orders/stats/summary (fromDate?: string, toDate?: string) -> Order statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/summary`","tags":["Sales channels"]}},"/sales-channel/pricing/rules":{"get":{"operationId":"PricingController_getPricingRules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"}],"responses":{"200":{"description":"Pricing rules","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List pricing rules","description":"Rules that derive a channel price from the base price — marketplace fee uplifts, per-channel margins, floors. Optionally filtered to one channel.\n\n#### Signature\n\n```http\nGET /sales-channel/pricing/rules (channel?: string) -> Pricing rules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/pricing/rules`","tags":["Sales channels"]},"post":{"operationId":"PricingController_savePricingRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Save a pricing rule","description":"Creates or replaces a pricing rule. Rules change what customers are charged on live marketplaces the next time prices sync — calculate first and check the numbers before syncing.\n\n#### Signature\n\n```http\nPOST /sales-channel/pricing/rules (body) -> The saved rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Affects live selling prices once synced.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/pricing/calculate/all`","tags":["Sales channels"],"requestBody":{"description":"The rule.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Amazon fee uplift","channel":"amazon","adjustmentType":"percentage","adjustmentValue":15,"minMargin":10}}}}}},"/sales-channel/pricing/rules/{ruleId}":{"get":{"operationId":"PricingController_getPricingRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ruleId","required":true,"in":"path","schema":{"type":"string"},"description":"Rule id.","example":"PR-12"}],"responses":{"200":{"description":"The rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a pricing rule","description":"Fetches one pricing rule.\n\n#### Signature\n\n```http\nGET /sales-channel/pricing/rules/{ruleId} (ruleId: string) -> The rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /sales-channel/pricing/rules/{ruleId}`","tags":["Sales channels"]},"delete":{"operationId":"PricingController_deletePricingRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ruleId","required":true,"in":"path","schema":{"type":"string"},"description":"Rule id.","example":"PR-12"}],"responses":{"200":{"description":"The delete result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a pricing rule","description":"Removes a pricing rule. Prices already pushed to channels stay as they are until the next sync recalculates them without the rule — which can drop a marketplace price below the margin the rule was protecting.\n\n#### Signature\n\n```http\nDELETE /sales-channel/pricing/rules/{ruleId} (ruleId: string) -> The delete result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Re-sync deliberately after deleting a rule.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/pricing/sync/{channelId}`","tags":["Sales channels"]}},"/sales-channel/pricing/calculate/{channelId}":{"post":{"operationId":"PricingController_calculateChannelPrice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The calculated price and breakdown","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/pricing/calculate/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Calculate a channel price","description":"Runs the pricing rules for one channel against a product and returns the resulting price with its breakdown. Pure calculation — nothing is saved or pushed.\n\n#### Signature\n\n```http\nPOST /sales-channel/pricing/calculate/{channelId} (channelId: string, body) -> The calculated price and breakdown\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/pricing/calculate/all`","tags":["Sales channels"],"requestBody":{"description":"The product to price.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","price"],"properties":{"sku":{"type":"string","example":"COLA-330"},"price":{"type":"number","description":"Base price.","example":1.99},"cost":{"type":"number","description":"Enables margin rules.","example":0.8},"categoryId":{"type":"string"},"brand":{"type":"string"}}},"example":{"sku":"COLA-330","price":1.99,"cost":0.8}}}}}},"/sales-channel/pricing/calculate/all":{"post":{"operationId":"PricingController_calculateAllChannelPrices","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Per-channel prices","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Calculate prices for every channel","description":"Prices one product for all configured channels side by side — the preview to check before a price sync.\n\n#### Signature\n\n```http\nPOST /sales-channel/pricing/calculate/all (body) -> Per-channel prices\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/pricing/sync/{channelId}`","tags":["Sales channels"],"requestBody":{"description":"The product to price.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","price"],"properties":{"sku":{"type":"string"},"price":{"type":"number"},"cost":{"type":"number"},"categoryId":{"type":"string"},"brand":{"type":"string"}}},"example":{"sku":"COLA-330","price":1.99,"cost":0.8}}}}}},"/sales-channel/pricing/sync/{channelId}":{"post":{"operationId":"PricingController_syncPricesToChannel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The sync result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/pricing/sync/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Push prices to a channel","description":"Recalculates and pushes prices to a channel. Omit `skus` and the **whole catalogue** is repriced on that marketplace — run `calculate/all` on a sample first.\n\n#### Signature\n\n```http\nPOST /sales-channel/pricing/sync/{channelId} (channelId: string, body) -> The sync result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- An empty body reprices the entire catalogue on that channel.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/pricing/calculate/all`","tags":["Sales channels"],"requestBody":{"description":"Optional SKU list; omit for the whole catalogue.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"skus":{"type":"array","items":{"type":"string"},"example":["COLA-330"]}}},"examples":{"some":{"summary":"Specific SKUs","value":{"skus":["COLA-330","COLA-500"]}},"all":{"summary":"Entire catalogue","description":"Reprices everything on the marketplace.","value":{}}}}}}}},"/sales-channel/pricing/history/{sku}":{"get":{"operationId":"PricingController_getPriceHistory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"COLA-330"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Price history, newest first","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get price history for a SKU","description":"What a SKU has been priced at over time, optionally on one channel — the record for answering why a customer was charged what they were.\n\n#### Signature\n\n```http\nGET /sales-channel/pricing/history/{sku} (sku: string, channel?: string, limit?: integer) -> Price history, newest first\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/pricing/compare/{sku}`","tags":["Sales channels"]}},"/sales-channel/pricing/competitor":{"post":{"operationId":"PricingController_saveCompetitorPrice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The recorded price","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record a competitor price","description":"Stores an observed competitor price for a SKU. Feeds the comparison view and any repricing rules that key off competitor data — it is an observation, so record where it came from.\n\n#### Signature\n\n```http\nPOST /sales-channel/pricing/competitor (body) -> The recorded price\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/pricing/competitor/{sku}`","tags":["Sales channels"],"requestBody":{"description":"The observation.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sku","competitor","price"],"properties":{"sku":{"type":"string","example":"COLA-330"},"competitor":{"type":"string","example":"BigMart"},"price":{"type":"number","example":1.79},"url":{"type":"string","example":"https://bigmart.example.com/cola-330"}}},"example":{"sku":"COLA-330","competitor":"BigMart","price":1.79,"url":"https://bigmart.example.com/cola-330"}}}}}},"/sales-channel/pricing/competitor/{sku}":{"get":{"operationId":"PricingController_getCompetitorPrices","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"COLA-330"}],"responses":{"200":{"description":"Competitor prices","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get competitor prices for a SKU","description":"Recorded competitor prices for one SKU. Each carries its observation time — treat older entries as stale rather than current market truth.\n\n#### Signature\n\n```http\nGET /sales-channel/pricing/competitor/{sku} (sku: string) -> Competitor prices\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/pricing/competitor`","tags":["Sales channels"]}},"/sales-channel/pricing/compare/{sku}":{"get":{"operationId":"PricingController_getPriceComparison","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"COLA-330"}],"responses":{"200":{"description":"The comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Compare a SKU's prices","description":"The org's price on each channel alongside recorded competitor prices — the one view for deciding whether a SKU is mispriced.\n\n#### Signature\n\n```http\nGET /sales-channel/pricing/compare/{sku} (sku: string) -> The comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/pricing/history/{sku}`","tags":["Sales channels"]}},"/sales-channel/analytics/metrics/sync/{channelId}":{"post":{"operationId":"ChannelAnalyticsController_syncSalesMetrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The sync result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/analytics/metrics/sync/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Sync sales metrics from a channel","description":"Pulls the channel's own sales figures into the analytics store, so channel reporting reflects marketplace-side numbers rather than only locally aggregated orders.\n\n#### Signature\n\n```http\nPOST /sales-channel/analytics/metrics/sync/{channelId} (channelId: string) -> The sync result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/metrics`","tags":["Sales channels"]}},"/sales-channel/analytics/metrics/{channelId}":{"post":{"operationId":"ChannelAnalyticsController_recordSalesMetrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The stored metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record sales metrics","description":"Writes sales metrics for a channel directly — for channels with no metrics API, or for back-filling. Manually written figures sit alongside synced ones and are not distinguished in reports.\n\n#### Signature\n\n```http\nPOST /sales-channel/analytics/metrics/{channelId} (channelId: string, body) -> The stored metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sales-channel/analytics/metrics/sync/{channelId}`","tags":["Sales channels"],"requestBody":{"description":"The metrics.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"revenue":12400,"orders":320,"units":890}}}}}},"/sales-channel/analytics/metrics":{"get":{"operationId":"ChannelAnalyticsController_getSalesMetrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"Metrics","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get sales metrics","description":"Sales metrics over a date range, optionally for one channel.\n\n#### Signature\n\n```http\nGET /sales-channel/analytics/metrics (channel?: string, fromDate?: string, toDate?: string) -> Metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/summary`","tags":["Sales channels"]}},"/sales-channel/analytics/products/{sku}/{channelId}":{"post":{"operationId":"ChannelAnalyticsController_recordProductPerformance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":true,"in":"path","schema":{"type":"string"},"description":"Product SKU.","example":"COLA-330"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The stored metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record product performance","description":"Writes performance metrics for one SKU on one channel — views, conversion, units.\n\n#### Signature\n\n```http\nPOST /sales-channel/analytics/products/{sku}/{channelId} (sku: string, channelId: string, body) -> The stored metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/products`","tags":["Sales channels"],"requestBody":{"description":"The metrics.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"views":4200,"unitsSold":118,"conversionRate":2.8}}}}}},"/sales-channel/analytics/products":{"get":{"operationId":"ChannelAnalyticsController_getProductPerformance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":false,"in":"query","schema":{"type":"string"},"example":"COLA-330"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"Product performance","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get product performance","description":"Per-product performance across channels — which SKUs sell where, and which listings get traffic without converting.\n\n#### Signature\n\n```http\nGET /sales-channel/analytics/products (sku?: string, channel?: string, fromDate?: string, toDate?: string) -> Product performance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/compare`","tags":["Sales channels"]}},"/sales-channel/analytics/summary":{"get":{"operationId":"ChannelAnalyticsController_getChannelSummary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"The summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an analytics summary","description":"Headline figures across all channels for a date range.\n\n#### Signature\n\n```http\nGET /sales-channel/analytics/summary (fromDate?: string, toDate?: string) -> The summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/dashboard`","tags":["Sales channels"]}},"/sales-channel/analytics/dashboard":{"get":{"operationId":"ChannelAnalyticsController_getDashboardMetrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"The dashboard payload","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get the channel dashboard","description":"The composed dashboard payload — summary, per-channel breakdown and top products in one response, so a dashboard renders from a single call.\n\n#### Signature\n\n```http\nGET /sales-channel/analytics/dashboard (fromDate?: string, toDate?: string) -> The dashboard payload\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/summary`","tags":["Sales channels"]}},"/sales-channel/analytics/compare":{"get":{"operationId":"ChannelAnalyticsController_getChannelComparison","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channels","required":true,"in":"query","schema":{"type":"string"},"description":"Comma-separated channel ids.","example":"amazon,ebay,etsy"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date, inclusive.","example":"2026-08-31"}],"responses":{"200":{"description":"The comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Compare channels","description":"Puts named channels side by side over the same date range — the read for deciding where a product actually earns its margin.\n\n#### Signature\n\n```http\nGET /sales-channel/analytics/compare (channels?: string, fromDate?: string, toDate?: string) -> The comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/analytics/dashboard`","tags":["Sales channels"]}},"/sales-channel/optimization/analyze/{channelId}":{"post":{"operationId":"ListingOptimizationController_analyzeProduct","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"Score and suggestions","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/optimization/analyze/{channelId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Analyse a listing","description":"Scores one product's listing for a channel and returns improvement suggestions — title length, missing attributes, image count, keyword coverage. Analysis only; nothing is changed until a suggestion is applied.\n\n#### Signature\n\n```http\nPOST /sales-channel/optimization/analyze/{channelId} (channelId: string, body) -> Score and suggestions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/optimization/suggestions`","tags":["Sales channels"],"requestBody":{"description":"The product to analyse.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sku":"COLA-330","title":"Cola 330ml","description":"Refreshing cola."}}}}}},"/sales-channel/optimization/analyze/{channelId}/bulk":{"post":{"operationId":"ListingOptimizationController_bulkAnalyze","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channelId","required":true,"in":"path","schema":{"type":"string"},"description":"Channel identifier, e.g. `amazon`, `ebay`, `etsy`, `walmart`.","example":"amazon"}],"responses":{"201":{"description":"The analysis result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown channel: <channelId> — The channel id is not one the platform supports.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown channel: <channelId>","path":"/sales-channel/optimization/analyze/{channelId}/bulk","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Analyse the whole catalogue","description":"Runs listing analysis across every product for a channel and stores the scores and suggestions. Catalogue-wide, so it can run for a while on a large catalogue.\n\n#### Signature\n\n```http\nPOST /sales-channel/optimization/analyze/{channelId}/bulk (channelId: string) -> The analysis result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_CHANNEL | Unknown channel: <channelId> | The channel id is not one the platform supports. | List supported channels with `GET /sales-channel/channels`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/optimization/scores`","tags":["Sales channels"]}},"/sales-channel/optimization/scores":{"get":{"operationId":"ListingOptimizationController_getListingScores","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":false,"in":"query","schema":{"type":"string"},"example":"COLA-330"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"}],"responses":{"200":{"description":"Scores","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List listing scores","description":"Stored listing-quality scores, filterable by SKU or channel — where to focus listing work.\n\n#### Signature\n\n```http\nGET /sales-channel/optimization/scores (sku?: string, channel?: string) -> Scores\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/optimization/summary`","tags":["Sales channels"]}},"/sales-channel/optimization/suggestions":{"get":{"operationId":"ListingOptimizationController_getSuggestions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sku","required":false,"in":"query","schema":{"type":"string"},"example":"COLA-330"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"example":"amazon"},{"name":"priority","required":false,"in":"query","schema":{"type":"string"},"example":"high"},{"name":"applied","required":false,"in":"query","schema":{"type":"boolean"},"description":"Filter by whether the suggestion has been applied.","example":false}],"responses":{"200":{"description":"Suggestions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List optimisation suggestions","description":"Individual suggestions from listing analysis, filterable by SKU, channel, priority and whether they have already been applied. Filter `applied=false` for the outstanding work.\n\n#### Signature\n\n```http\nGET /sales-channel/optimization/suggestions (sku?: string, channel?: string, priority?: string, applied?: boolean) -> Suggestions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /sales-channel/optimization/suggestions/{suggestionId}/apply`","tags":["Sales channels"]}},"/sales-channel/optimization/suggestions/{suggestionId}/apply":{"put":{"operationId":"ListingOptimizationController_markSuggestionApplied","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"suggestionId","required":true,"in":"path","schema":{"type":"string"},"description":"Suggestion id.","example":"SUG-4821"}],"responses":{"200":{"description":"The applied suggestion","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Apply an optimisation suggestion","description":"Applies a suggestion to the listing and marks it applied. This edits real listing content — review the suggestion before applying, particularly anything that rewrites a title or description.\n\n#### Signature\n\n```http\nPUT /sales-channel/optimization/suggestions/{suggestionId}/apply (suggestionId: string) -> The applied suggestion\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Modifies listing content.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/optimization/suggestions`","tags":["Sales channels"]}},"/sales-channel/optimization/summary":{"get":{"operationId":"ListingOptimizationController_getOptimizationSummary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an optimisation summary","description":"Aggregate listing health — average scores and outstanding suggestion counts by channel.\n\n#### Signature\n\n```http\nGET /sales-channel/optimization/summary () -> The summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sales-channel/optimization/scores`","tags":["Sales channels"]}},"/sync/social-activities/profiles/{platform}":{"get":{"operationId":"SocialActivityController_getProfiles","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","in":"path","required":true,"description":"Optional segment, accepted but **ignored** — only the `platform` query parameter is read. `GET /sync/social-activities/profiles` reaches the same handler.","schema":{"type":"string"},"example":"instagram"},{"name":"platform","in":"query","required":false,"description":"Comma-separated platforms to fetch, from `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`. Omit for every platform in the sync settings.","schema":{"type":"string"},"example":"facebook,instagram"}],"responses":{"200":{"description":"Account details keyed by platform","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"object","additionalProperties":true,"description":"The platform's account details, as it returned them."}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get connected social profiles","description":"Fetches the account details of the org's connected social accounts **live from each platform** — one call per platform, made now, not read from the activity store.\n\nName the platforms in the `platform` **query** parameter, comma-separated (`?platform=facebook,instagram`). Omit it and every platform in the org's social sync settings is fetched. For each platform the account asked about is the one saved for it in those settings, through the `default` integration config.\n\nThe result is an object keyed by platform; each value is what the platform returned for the account. A platform that fails — not in the sync settings, not connected, or refused by the platform — is left out, so a missing key means \"could not fetch\", not \"no account\".\n\n#### Signature\n\n```http\nGET /sync/social-activities/profiles/{platform} (platform: string, platform?: string) -> Account details keyed by platform\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `/profiles/instagram` returns every platform, not just Instagram — the path segment is not read. Use `?platform=instagram`.\n- With no `platform` and no social sync settings saved for the org, the call fails with a 500 rather than returning `{}`.\n- Each call goes to the platforms and counts against their rate limits.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platforms`","tags":["CRM · Social"]}},"/sync/social-activities/reply":{"post":{"operationId":"SocialActivityController_replyToActivity","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`success: true` and the page or account it was sent from; or `success: false` with the reason (e.g. the message was not found, no connected page, or the platform refused it).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"sentFrom":{"type":"string","description":"The page or account the reply was sent from.","example":"115816908447835"},"error":{"type":"string","description":"Why it was not sent, when `success` is false."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Reply to a social message or comment","description":"Sends a reply to a customer on the platform they wrote on — a direct message stays a direct message, a comment reply goes on the comment — and records it in the conversation thread.\n\nName the message being answered by our own id (`activityId`, the `social_activity` record) and give the reply. The server works out everything else from that record: the platform, whether it is a message or a comment, the customer to send to, the page or account they wrote to (the reply is sent from it), and the comment and post ids. The reply is signed with the name of the signed-in user.\n\nThe reply is recorded as an outbound `social_activity` (`isAiGenerated: false`), so it shows in the thread and the platform's echo of the same message is not recorded twice.\n\n#### Signature\n\n```http\nPOST /sync/social-activities/reply (body) -> `success: true` and the page or account it was sent from; or `success: false` with the reason (e.g. the message was not found, no connected page, or the platform refused it).\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A reply to a public comment is public. Move anything sensitive to a direct message first.\n- Older callers may still send platform, to, pageId, commentId and postId themselves; with `activityId` those are worked out by the server.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/conversation-thread`\n- `POST /sync/social-activities/ai-hold`","tags":["CRM · Social"],"requestBody":{"description":"The message being answered, and the reply.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["activityId"],"properties":{"activityId":{"type":"string","description":"Our id of the message or comment being answered (a `social_activity` sk) — usually the customer's latest message in the thread.","example":"6ab60d3a4501ae658d446b10"},"message":{"type":"string","description":"The reply text. Required unless attachments are sent.","example":"It is back in stock now — thanks for waiting!"},"attachments":{"type":"array","items":{"type":"string"},"description":"Optional file URLs to send with the reply (direct messages)."},"note":{"type":"string","description":"Optional handover note kept on the recorded reply — what was done or decided, for whoever picks the conversation up next."}}},"example":{"activityId":"6ab60d3a4501ae658d446b10","message":"It is back in stock now — thanks for waiting!"}}}}}},"/sync/social-activities/ai-hold":{"post":{"operationId":"SocialActivityController_setAiHold","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The new state","content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","example":"instagram"},"customerId":{"type":"string","example":"17841400000000000"},"aiHold":{"type":"boolean","description":"`true` when AI replies are now paused.","example":true},"by":{"type":"string","description":"The signed-in user's email (`agent` if it has none).","example":"ada@example.com"},"at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"platform and customerId are required — `platform` or `customerId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"platform and customerId are required","path":"/sync/social-activities/ai-hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pause or resume AI replies to a customer","description":"Pauses or resumes AI replies to one customer on one platform, so a person can handle the conversation without an assistant answering over them.\n\nIt holds **replies only**. Assistants still run on the conversation — tickets, notes and notifications still happen — they just do not message the customer. A hold never expires: it stays until someone resumes it.\n\nEvery call adds a record to the conversation (`social_activity` with `standardActivityType: ai-hold`); nothing is updated or deleted, so the trail of who paused and resumed, and why, is kept. The current state is the latest record. The signed-in user's email is recorded as who did it.\n\n#### Signature\n\n```http\nPOST /sync/social-activities/ai-hold (body) -> The new state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A hold is per customer id per platform — it covers that customer on every one of our accounts on the platform. The same person on another platform has a different id and still gets AI replies.\n- Pausing twice is harmless: it adds a second record and the state stays paused.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | HOLD_TARGET_REQUIRED | platform and customerId are required | `platform` or `customerId` is missing. | Send both — a hold is always for one customer on one platform. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/ai-hold`\n- `POST /sync/social-activities/reply`","tags":["CRM · Social"],"requestBody":{"description":"Whose replies to pause or resume.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["platform","customerId"],"properties":{"platform":{"type":"string","description":"The platform, e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"},"customerId":{"type":"string","description":"Platform id of the customer.","example":"17841400000000000"},"hold":{"type":"boolean","default":true,"description":"`false` resumes AI replies. Anything else — including leaving it out — pauses them.","example":true},"reason":{"type":"string","description":"Why. Kept in the trail and shown in the record's text (\"AI replies paused: …\").","example":"Complaint — needs a human"},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on. Stored on the record only — it does not narrow the hold.","example":"115816908447835"}}},"examples":{"hold":{"summary":"Pause AI replies","value":{"platform":"instagram","customerId":"17841400000000000","hold":true,"reason":"Complaint — needs a human"}},"release":{"summary":"Resume AI replies","value":{"platform":"instagram","customerId":"17841400000000000","hold":false}}}}}}},"get":{"operationId":"SocialActivityController_getAiHold","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":true,"in":"query","schema":{"type":"string"},"description":"The platform.","example":"instagram"},{"name":"customerId","required":true,"in":"query","schema":{"type":"string"},"description":"Platform id of the customer.","example":"17841400000000000"}],"responses":{"200":{"description":"Current state and the trail","content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","example":"instagram"},"customerId":{"type":"string","example":"17841400000000000"},"aiHold":{"type":"boolean","description":"Whether AI replies are paused now (the latest action was a pause).","example":true},"since":{"type":"string","format":"date-time","nullable":true,"description":"When the latest action was taken; `null` if there has never been one."},"by":{"type":"string","nullable":true,"description":"Who took the latest action.","example":"ada@example.com"},"trail":{"type":"array","description":"The last 20 pause/resume actions, newest first.","items":{"type":"object","properties":{"at":{"type":"string","format":"date-time"},"hold":{"type":"boolean","description":"`true` for a pause, `false` for a resume."},"by":{"type":"string","nullable":true},"reason":{"type":"string","nullable":true}}}}}},"example":{"platform":"instagram","customerId":"17841400000000000","aiHold":true,"since":"2026-09-24T14:05:00.000Z","by":"ada@example.com","trail":[{"at":"2026-09-24T14:05:00.000Z","hold":true,"by":"ada@example.com","reason":"Complaint — needs a human"}]}}}},"400":{"description":"platform and customerId are required — `platform` or `customerId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"platform and customerId are required","path":"/sync/social-activities/ai-hold","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get AI hold state for a customer","description":"Whether AI replies to one customer on one platform are paused, and the trail behind it — who paused or resumed, when and why — so an agent can tell whether the pause still makes sense.\n\n#### Signature\n\n```http\nGET /sync/social-activities/ai-hold (platform?: string, customerId?: string) -> Current state and the trail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- If the lookup itself fails the answer is \"not paused\" with an empty trail, not an error.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | HOLD_TARGET_REQUIRED | platform and customerId are required | `platform` or `customerId` is missing. | Send both — a hold is always for one customer on one platform. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sync/social-activities/ai-hold`","tags":["CRM · Social"]}},"/sync/social-activities/conversations":{"get":{"operationId":"SocialActivityController_getSocialConversations","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"account","required":false,"in":"query","schema":{"type":"string"},"description":"Only conversations on this connected account (page or account id). `all`, or leaving it out, means every account.","example":"115816908447835"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":0},"description":"Page number, **0-based**.","example":0},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":30},"description":"How many to return. Capped at 100; `0` or omitted gives 30.","example":30}],"responses":{"200":{"description":"One page of conversations","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The conversation's grouping key: `platform|ourId|customerId`, with the post id before the customer id for comment threads.","example":"instagram|17841400000000001|17841400000000000"},"platform":{"type":"string","example":"instagram"},"ourId":{"type":"string","description":"Our page or account the conversation is on.","example":"17841400000000001"},"accountName":{"type":"string","nullable":true,"description":"Display name of that account, from the records or the connected integrations; `null` when unknown.","example":"Acme Retail"},"customerId":{"type":"string","description":"Platform id of the customer. Can be empty for comment threads where the platform gave no commenter id.","example":"17841400000000000"},"customerName":{"type":"string","description":"The customer's name as last seen; falls back to `customerId`.","example":"ada.lovelace"},"activityType":{"type":"string","enum":["message","comment"],"description":"`comment` for a comment thread, otherwise `message`.","example":"message"},"postId":{"type":"string","description":"The post, for comment threads."},"lastMessageDate":{"type":"number","description":"Epoch milliseconds of the latest activity.","example":1788000000000},"lastMessageText":{"type":"string","example":"Is this back in stock?"},"messageCount":{"type":"integer","example":6},"hasAI":{"type":"boolean","description":"Whether an assistant has replied anywhere in the conversation."}}},"description":"The conversations on this page."},"page":{"type":"integer","description":"The page returned, 0-based.","example":0},"pageSize":{"type":"integer","description":"The page size used, after the default and cap.","example":30},"hasMore":{"type":"boolean","description":"`true` when the page came back full. It can be `true` on the last page when the count divides exactly — the next page is then empty."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List social conversations","description":"The social inbox: message, comment and mention activity grouped into conversations, the most recently active first. Grouping, the account filter, account names and paging are all done on the server.\n\nA direct-message conversation is one customer on one of our accounts on one platform. Comments are also split by post, so one person commenting on two posts is two conversations.\n\nSeparate from `/crm/inbox`, which covers email, SMS and chat.\n\n#### Signature\n\n```http\nGET /sync/social-activities/conversations (account?: string, page?: integer, pageSize?: integer) -> One page of conversations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Pages are **0-based** here, unlike most list endpoints, and no `total` is returned — page on until `hasMore` is `false`.\n- The account filter matches the account id on the record, the author or recipient id, or a post id that starts with `<account>_`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/conversation-thread`\n- `GET /crm/inbox/conversations`","tags":["CRM · Social"]}},"/sync/social-activities/conversation-thread":{"get":{"operationId":"SocialActivityController_getSocialThread","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":true,"in":"query","schema":{"type":"string"},"description":"The platform. Not checked by the server, but always send it — without it nothing from a real platform matches.","example":"instagram"},{"name":"customer","required":false,"in":"query","schema":{"type":"string"},"description":"Platform id of the customer. Needed unless `postId` is given.","example":"17841400000000000"},{"name":"postId","required":false,"in":"query","schema":{"type":"string"},"description":"The post, for a comment thread.","example":"18021234567890"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":0},"description":"Page number, **0-based**.","example":0},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":100},"description":"How many to return. Capped at 200; `0` or omitted gives 100.","example":100}],"responses":{"200":{"description":"One page of the thread, oldest first","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"The messages on this page. A message someone reacted to carries `data.reactions: [{ emoji, reaction, authorId }]`."},"page":{"type":"integer","description":"The page returned, 0-based.","example":0},"pageSize":{"type":"integer","description":"The page size used, after the default and cap.","example":30},"hasMore":{"type":"boolean","description":"`true` when the page came back full. It can be `true` on the last page when the count divides exactly — the next page is then empty."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a social conversation thread","description":"The messages in one conversation, both sides, oldest first.\n\nFor a direct-message thread pass the customer's id: that matches what they sent us and what we sent them. For a comment thread pass `postId`: that returns the comments on the post, narrowed to one commenter when `customer` is given too. Instagram comment webhooks often carry no commenter id, so `postId` alone is the usual call there.\n\nOnly `message`, `comment` and `mention` rows are returned — the per-conversation summary row the sync writes, and AI-hold records, are left out. Reactions to a message are attached to it as `data.reactions`: the latest reaction from each person, with withdrawn reactions dropped.\n\n#### Signature\n\n```http\nGET /sync/social-activities/conversation-thread (platform?: string, customer?: string, postId?: string, page?: integer, pageSize?: integer) -> One page of the thread, oldest first\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- With neither `customer` nor `postId` — or with them sent as the strings `null` or `undefined` — the result is an empty page, not an error.\n- Pages are **0-based**.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sync/social-activities/reply`\n- `GET /sync/social-activities/conversations`","tags":["CRM · Social"]}},"/sync/social-activities/messages":{"get":{"operationId":"SocialActivityController_getAllMessages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The first page of matching social activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social messages","description":"Direct messages across social platforms — both what customers sent and the replies we sent. These are **not** in the `/crm/inbox` store; social DMs live in `social_activity`.\n\nMatches `standardActivityType` = `message`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/messages (startDate?: string, endDate?: string, platform?: string) -> The first page of matching social activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/comments":{"get":{"operationId":"SocialActivityController_getAllComments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The first page of matching social activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social comments","description":"Comments on the org's social posts, across platforms — customers' comments and our replies to them.\n\nMatches `standardActivityType` = `comment`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/comments (startDate?: string, endDate?: string, platform?: string) -> The first page of matching social activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/posts":{"get":{"operationId":"SocialActivityController_getAllPosts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The posts in unified feed form","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"A generated label — the platform name plus \" Page\" (e.g. `Instagram Page`), or `Social Media Page` with no platform. Not the account's real name.","example":"Instagram Page"},"picture":{"type":"object","description":"Always `{ data: { url: \"\" } }` — no profile picture is looked up."},"platform":{"type":"string","description":"The platform asked for; absent when none was.","example":"instagram"},"feed":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The platform's id for the item (`sourceId`).","example":"18021234567890"},"message":{"type":"string","description":"The text — taken from `content`, `message`, `text` or `description`, whichever is set."},"created_time":{"description":"From `createdAt`, `created_time` or `timestamp`, as stored (a string or epoch milliseconds)."},"likes":{"type":"object","properties":{"summary":{"type":"object","properties":{"total_count":{"type":"integer","example":42}}}}},"comments":{"type":"object","properties":{"summary":{"type":"object","properties":{"total_count":{"type":"integer","example":7}}}}},"shares":{"type":"object","properties":{"count":{"type":"integer","example":3}}},"attachments":{"type":"object","properties":{"data":{"type":"array","description":"Built from `mediaUrls`, `picture`, `full_picture` and `video`.","items":{"type":"object","properties":{"media":{"type":"object","description":"`{ image: { src } }` or `{ video: { src } }`."},"type":{"type":"string","description":"`photo`, `video`, `link` or `unknown` — guessed from the URL's file extension.","example":"photo"}}}}}},"author":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"username":{"type":"string"},"picture":{"type":"string"}}},"platform":{"type":"string","example":"instagram"},"sourceType":{"type":"string","example":"instagram-post"},"url":{"type":"string"},"views":{"type":"integer","example":0},"reach":{"type":"integer","example":0},"impressions":{"type":"integer","example":0}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social posts","description":"The org's own posts (`standardActivityType` `post` or `article`), reshaped into a single \"unified feed\" layout for display: one object with the posts under `feed.data`, each post with its counts, attachments and author in a fixed shape whatever platform it came from.\n\n#### Signature\n\n```http\nGET /sync/social-activities/posts (startDate?: string, endDate?: string, platform?: string) -> The posts in unified feed form\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- At most 50 posts, most recently modified first; there is no paging and no total.\n- Unlike the other feeds this does not return the stored records — only the unified shape.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/dashboard/analytics/{platform}`\n- `GET /sync/social-activities/refresh/{platform}/{action}`","tags":["CRM · Social"]}},"/sync/social-activities/engagement":{"get":{"operationId":"SocialActivityController_getAllEngagement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The first page of matching social activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get engagement activity","description":"Likes, reactions, shares and retweets recorded across platforms.\n\nMatches `standardActivityType` of `like`, `reaction`, `share`, `retweet`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/engagement (startDate?: string, endDate?: string, platform?: string) -> The first page of matching social activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/leads":{"get":{"operationId":"SocialActivityController_getAllLeads","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The first page of matching social activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social leads","description":"Leads captured from social platforms, such as lead-ad form fills.\n\nMatches `standardActivityType` = `lead`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/leads (startDate?: string, endDate?: string, platform?: string) -> The first page of matching social activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/ads":{"get":{"operationId":"SocialActivityController_getAllAds","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The first page of matching social activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social ad activity","description":"Ad records synced into the social store — distinct from the campaign management under `/crm/ads`.\n\nMatches `standardActivityType` = `ad`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/ads (startDate?: string, endDate?: string, platform?: string) -> The first page of matching social activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/campaigns":{"get":{"operationId":"SocialActivityController_getAllCampaigns","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The first page of matching social activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social campaign activity","description":"Campaign records synced into the social store.\n\nMatches `standardActivityType` = `campaign`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/campaigns (startDate?: string, endDate?: string, platform?: string) -> The first page of matching social activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/analytics":{"get":{"operationId":"SocialActivityController_getAllAnalytics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one platform (exact match), e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"}],"responses":{"200":{"description":"The first page of matching social activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social analytics records","description":"The analytics, insight and metric records synced from the platforms, as stored — not aggregated.\n\nMatches `standardActivityType` of `analytics`, `insight`, `metric`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/analytics (startDate?: string, endDate?: string, platform?: string) -> The first page of matching social activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/platform/{platform}/type/{activityType}":{"get":{"operationId":"SocialActivityController_getActivitiesByPlatformAndType","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":true,"in":"path","schema":{"type":"string"},"description":"The platform, e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"},{"name":"activityType","required":true,"in":"path","schema":{"type":"string"},"description":"A standard activity type — `GET /sync/social-activities/platforms` lists them all. Not validated: an unknown type returns an empty page.","example":"mention"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"}],"responses":{"200":{"description":"The first page of matching activity","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get activity by platform and type","description":"Activity of one `standardActivityType` on one platform — for combinations the fixed feeds do not cover, such as `story`, `reel`, `mention` or `follower`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/platform/{platform}/type/{activityType} (platform: string, activityType: string, startDate?: string, endDate?: string) -> The first page of matching activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first page comes back: at most 50 records, ordered by when the record was last modified (not by `timestamp`). There is no paging parameter — narrow with the date range or platform, and read `total` for how many matched.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sync/social-activities/search`\n- `GET /sync/social-activities/platforms`","tags":["CRM · Social"]}},"/sync/social-activities/summary/platforms":{"get":{"operationId":"SocialActivityController_getActivitySummaryByPlatform","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"}],"responses":{"200":{"description":"One row per platform","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","description":"The platform.","example":"instagram"},"totalActivities":{"type":"integer","example":1240},"activities":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","nullable":true,"description":"The `standardActivityType`.","example":"comment"},"count":{"type":"integer","example":380},"latestActivity":{"type":"number","description":"Latest `timestamp` of that type, epoch milliseconds.","example":1788000000000}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a per-platform summary","description":"How many activity records each platform has, broken down by activity type, busiest platform first.\n\n#### Signature\n\n```http\nGET /sync/social-activities/summary/platforms (startDate?: string, endDate?: string) -> One row per platform\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Give either date alone and the other is filled in: `startDate` defaults to the beginning of time, `endDate` to now.\n- If the summary cannot be computed the response is `[]`, not an error.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/dashboard/analytics`\n- `GET /sync/social-activities/platforms`","tags":["CRM · Social"]}},"/sync/social-activities/metrics/engagement":{"get":{"operationId":"SocialActivityController_getEngagementMetrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"}],"responses":{"200":{"description":"One row per platform","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","description":"The platform.","example":"instagram"},"platform":{"type":"string","example":"instagram"},"totalLikes":{"type":"integer","example":820},"totalComments":{"type":"integer","example":380},"totalShares":{"type":"integer","example":45},"totalReactions":{"type":"integer","example":120},"totalEngagement":{"type":"integer","description":"The four totals added up.","example":1365}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get engagement metrics","description":"Engagement counts per platform over a period: the number of like, comment, share and reaction records, and their sum. Counts of records, not rates. Most engaged platform first.\n\n#### Signature\n\n```http\nGET /sync/social-activities/metrics/engagement (startDate?: string, endDate?: string) -> One row per platform\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Give either date alone and the other is filled in: `startDate` defaults to the beginning of time, `endDate` to now.\n- Retweets are not counted in `totalShares`.\n- If the metrics cannot be computed the response is `[]`, not an error.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/metrics/business`\n- `GET /sync/social-activities/dashboard/analytics`","tags":["CRM · Social"]}},"/sync/social-activities/metrics/business":{"get":{"operationId":"SocialActivityController_getBusinessMetrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"}],"responses":{"200":{"description":"One row per platform","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","description":"The platform.","example":"facebook"},"platform":{"type":"string","example":"facebook"},"totalAds":{"type":"integer","example":12},"totalCampaigns":{"type":"integer","example":3},"totalLeads":{"type":"integer","example":57},"totalSpend":{"type":"number","description":"Sum of the records' `cost`.","example":1840.5},"totalReach":{"type":"number","example":52000},"totalImpressions":{"type":"number","example":88000},"totalClicks":{"type":"number","example":1320},"ctr":{"type":"number","description":"Clicks ÷ impressions, as a fraction (0.015 = 1.5%). `0` with no impressions.","example":0.015}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get business metrics from social","description":"Ad, campaign and lead figures per platform over a period: how many of each record, and the spend, reach, impressions and clicks summed from them. Highest spend first.\n\n#### Signature\n\n```http\nGET /sync/social-activities/metrics/business (startDate?: string, endDate?: string) -> One row per platform\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Give either date alone and the other is filled in: `startDate` defaults to the beginning of time, `endDate` to now.\n- If the metrics cannot be computed the response is `[]`, not an error.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/metrics/engagement`\n- `GET /sync/social-activities/dashboard/analytics`","tags":["CRM · Social"]}},"/sync/social-activities/search":{"post":{"operationId":"SocialActivityController_searchActivitiesByContent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Matching activity — at most 50, no total","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search social activity","description":"Searches social activity by text, with optional platform, type and date filters.\n\nWith `searchTerm`, it runs a full-text search, best matches first. If that finds nothing it falls back to a case-insensitive \"contains\" match on the text — and that fallback **drops the other filters**, so it can return activity from other platforms, types or dates. Without `searchTerm`, only the filters apply, most recently modified first.\n\n#### Signature\n\n```http\nPOST /sync/social-activities/search (body) -> Matching activity — at most 50, no total\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the first 50 matches come back and there is no paging.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/platform/{platform}/type/{activityType}`","tags":["CRM · Social"],"requestBody":{"description":"What to search for. Every field is optional; an empty body returns the most recently modified activity.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"searchTerm":{"type":"string","description":"Text to search for.","example":"refund"},"activityTypes":{"type":"array","items":{"type":"string"},"description":"Only these `standardActivityType` values.","example":["comment","message"]},"platforms":{"type":"array","items":{"type":"string"},"description":"Only these platforms.","example":["instagram"]},"startDate":{"type":"string","description":"Start of the period. Applied only when `endDate` is given too.","example":"2026-08-01"},"endDate":{"type":"string","description":"End of the period. Applied only when `startDate` is given too.","example":"2026-09-01"}}},"example":{"searchTerm":"refund","platforms":["instagram"],"activityTypes":["comment"]}}}}}},"/sync/social-activities/author/{authorId}":{"get":{"operationId":"SocialActivityController_getActivitiesByAuthor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"authorId","required":true,"in":"path","schema":{"type":"string"},"description":"Platform id of the author.","example":"17841400000000000"},{"name":"authorName","required":false,"in":"query","schema":{"type":"string"},"description":"Also match activity whose author name contains this, case-insensitively. Read as a regular expression.","example":"ada"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Meant to restrict to one platform — currently empties the result instead (see above).","example":"instagram"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Meant to bound the period — currently empties the result instead (see above).","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"Meant to bound the period — currently empties the result instead (see above).","example":"2026-09-01"}],"responses":{"200":{"description":"A one-element array holding the page of the author's activity; `[]` when a filter is sent","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A social activity (`social_activity`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"description":"Fields vary by platform and by how the row was written; these are the ones the server itself reads.","properties":{"platform":{"type":"string","example":"instagram"},"standardActivityType":{"type":"string","description":"The normalised type every feed filters on — `message`, `comment`, `mention`, `post`, `reaction`, `lead`, `ai-hold`, …","example":"comment"},"sourceType":{"type":"string","description":"Platform plus the raw kind of item.","example":"instagram-comment"},"sourceId":{"type":"string","description":"The platform's own id for the item.","example":"18021234567890123"},"timestamp":{"type":"number","description":"When it happened, epoch milliseconds. Date filters apply to this field.","example":1788000000000},"content":{"type":"string","example":"Is this back in stock?"},"authorId":{"type":"string","description":"Who produced it — the customer on their messages, our page or account on replies we sent.","example":"17841400000000000"},"authorName":{"type":"string","example":"ada.lovelace"},"recipientId":{"type":"string","description":"Who it was sent to — the customer, on replies we sent."},"accountId":{"type":"string","description":"Which of our connected accounts the conversation is on.","example":"115816908447835"},"postId":{"type":"string","description":"Post the activity belongs to, for comments.","example":"18021234567890"},"parentId":{"type":"string","description":"The item this one answers or reacts to."},"direction":{"type":"string","description":"`outbound` for replies we sent, `internal` for AI-hold rows. Customer activity is never `outbound`.","example":"outbound"},"isAiGenerated":{"type":"boolean","description":"On replies we sent: whether an assistant wrote it."}}}}},"description":"Up to 50 records, most recently modified first."},"total":{"type":"integer","description":"Every matching record, not just the ones returned.","example":312},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":50},"hasNext":{"type":"boolean","description":"`true` when more matched than were returned."},"datatype":{"type":"string","example":"social_activity"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get activity by author","description":"Activity one person authored — their messages, comments and engagement — matched on their platform id, or also on a name when `authorName` is given. Replies we sent them are not included (they are authored by us).\n\n**The response is not a plain list of activity.** It is a one-element array whose only item is the page object `{ data, total, page, pageSize, hasNext, … }`, with the activity in `[0].data` (first 50, most recently modified first). And because the `platform`, `startDate` and `endDate` filters are applied to that page object rather than to the activity, sending any of them returns `[]`.\n\n#### Signature\n\n```http\nGET /sync/social-activities/author/{authorId} (authorId: string, authorName?: string, platform?: string, startDate?: string, endDate?: string) -> A one-element array holding the page of the author's activity; `[]` when a filter is sent\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Author ids are per platform — the same person has a different id on each.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/conversation-thread`\n- `POST /sync/social-activities/search`","tags":["CRM · Social"]}},"/sync/social-activities/platforms":{"get":{"operationId":"SocialActivityController_getAvailablePlatforms","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Platforms and activity types","content":{"application/json":{"schema":{"type":"object","properties":{"platforms":{"type":"array","description":"Busiest first.","items":{"type":"object","properties":{"name":{"type":"string","example":"instagram"},"totalActivities":{"type":"integer","example":1240},"activityTypes":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","nullable":true,"description":"The `standardActivityType`; `null` for records without one.","example":"comment"},"count":{"type":"integer","example":380},"latestActivity":{"type":"string","format":"date-time","nullable":true,"description":"Latest `timestamp` of that type."}}}}}}},"standardActivityTypes":{"type":"array","items":{"type":"string"},"description":"Every standard activity type the server knows.","example":["post","comment","message","thread","story","reel"]},"totalPlatforms":{"type":"integer","example":3}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List platforms with activity","description":"The platforms that have activity in the store, with a count per activity type, plus the full list of standard activity types.\n\nBuilt from stored activity over all time, not from connections: a platform connected but not yet synced is missing, and one disconnected keeps appearing while its records remain.\n\n#### Signature\n\n```http\nGET /sync/social-activities/platforms () -> Platforms and activity types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/summary/platforms`\n- `GET /sync/social-activities/profiles/{platform}`","tags":["CRM · Social"]}},"/sync/social-activities/dashboard/analytics":{"get":{"operationId":"SocialActivityController_getDashboardAnalytics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"}],"responses":{"200":{"description":"Summary, engagement and business figures","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"object","properties":{"totalPlatforms":{"type":"integer","example":3},"totalActivities":{"type":"integer","example":2710},"platforms":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The rows of `GET /sync/social-activities/summary/platforms`."}}},"engagement":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The rows of `GET /sync/social-activities/metrics/engagement`."},"business":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The rows of `GET /sync/social-activities/metrics/business`."},"dateRange":{"type":"object","nullable":true,"description":"The period used, after defaults; `null` when no dates were given.","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get social dashboard analytics","description":"The per-platform summary, engagement metrics and business metrics in one call, over the same period — what a social dashboard needs.\n\n#### Signature\n\n```http\nGET /sync/social-activities/dashboard/analytics (startDate?: string, endDate?: string) -> Summary, engagement and business figures\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Give either date alone and the other is filled in: `startDate` defaults to the beginning of time, `endDate` to now.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/dashboard/analytics/{platform}`","tags":["CRM · Social"]}},"/sync/social-activities/dashboard/analytics/{platform}":{"get":{"operationId":"SocialActivityController_getPlatformDashboardAnalytics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":true,"in":"path","schema":{"type":"string"},"description":"The platform, e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period, as a date or ISO timestamp. Compared with the activity's `timestamp`. A value that is not a date matches nothing.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period, inclusive of that exact instant. A bare date is midnight UTC at the **start** of that day, so the day itself is left out — pass the next day, or a full timestamp, to include it.","example":"2026-09-01"}],"responses":{"200":{"description":"Post totals and recent posts","content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","example":"instagram"},"summary":{"type":"object","properties":{"totalPosts":{"type":"integer","example":50},"totalEngagement":{"type":"integer","description":"Likes + comments + shares.","example":1245},"totalLikes":{"type":"integer","example":1020},"totalComments":{"type":"integer","example":180},"totalShares":{"type":"integer","example":45},"totalViews":{"type":"integer","example":30500},"totalReach":{"type":"integer","example":21000},"engagementRate":{"type":"string","description":"Average engagement **per post** (not a percentage), as a string with two decimals; `\"0\"` with no posts.","example":"24.90"}}},"recentPosts":{"type":"array","description":"Up to 10 posts, most recently modified first.","items":{"type":"object","properties":{"id":{"type":"string","description":"The platform's post id."},"message":{"type":"string"},"created_time":{"description":"As stored on the post."},"likes":{"type":"integer"},"comments":{"type":"integer"},"shares":{"type":"integer"},"views":{"type":"integer"}}}},"dateRange":{"type":"object","nullable":true,"description":"The period used, after defaults; `null` when no dates were given.","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get dashboard analytics for one platform","description":"Post performance on one platform: totals of likes, comments, shares, views and reach, and the ten most recently modified posts.\n\nThe figures come from the counts stored on the post records themselves, and only from the **50 most recently modified** posts (`post` or `article`) in the period — on a busy account the totals cover those 50, not every post.\n\n#### Signature\n\n```http\nGET /sync/social-activities/dashboard/analytics/{platform} (platform: string, startDate?: string, endDate?: string) -> Post totals and recent posts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Give either date alone and the other is filled in: `startDate` defaults to the beginning of time, `endDate` to now.\n- An unknown platform returns zero totals and no posts, not an error.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/dashboard/analytics`\n- `GET /sync/social-activities/posts`","tags":["CRM · Social"]}},"/sync/social-activities/refresh/{platform}/{action}":{"get":{"operationId":"SocialActivityController_refreshPlatformData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":true,"in":"path","schema":{"type":"string"},"description":"The platform, e.g. `facebook`, `instagram`, `whatsapp`, `tiktok`, `twitter`, `linkedin`, `pinterest`.","example":"instagram"},{"name":"action","required":true,"in":"path","schema":{"type":"string"},"description":"What to fetch: `feed`, `posts`, `insights`, `analytics`, `comments`, `messages`, `notifications`, `reactions`, `engagement`, `leads`, `ads`, `campaigns`, `followers`, `profile`, or another provider `get…` operation.","example":"posts"},{"name":"configId","required":false,"in":"query","schema":{"type":"string"},"description":"Integration config to use. Defaults to the one in the sync settings, then `default`.","example":"default"},{"name":"accountId","required":false,"in":"query","schema":{"type":"string"},"description":"Account to fetch for, passed to the platform as `accountId`. Defaults to the account in the sync settings.","example":"115816908447835"},{"name":"limit","in":"query","required":false,"description":"Passed to the platform, like any other query parameter. Defaults to 25 for `feed` and `posts`.","schema":{"type":"integer"},"example":25}],"responses":{"200":{"description":"Always HTTP 200 — check `success`. On success, the saved items in unified feed form; on failure, the reason.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"platform":{"type":"string","description":"The platform asked for; absent when none was.","example":"instagram"},"action":{"type":"string","example":"posts"},"timestamp":{"type":"number","description":"Epoch milliseconds of the refresh.","example":1788000000000},"count":{"type":"integer","description":"Items the platform returned that had an id or name — including ones already stored and skipped.","example":25},"error":{"type":"string","description":"Why it failed, when `success` is false.","example":"No sync configuration found for platform: tiktok"},"name":{"type":"string","description":"A generated label — the platform name plus \" Page\" (e.g. `Instagram Page`), or `Social Media Page` with no platform. Not the account's real name.","example":"Instagram Page"},"picture":{"type":"object","description":"Always `{ data: { url: \"\" } }` — no profile picture is looked up."},"feed":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The platform's id for the item (`sourceId`).","example":"18021234567890"},"message":{"type":"string","description":"The text — taken from `content`, `message`, `text` or `description`, whichever is set."},"created_time":{"description":"From `createdAt`, `created_time` or `timestamp`, as stored (a string or epoch milliseconds)."},"likes":{"type":"object","properties":{"summary":{"type":"object","properties":{"total_count":{"type":"integer","example":42}}}}},"comments":{"type":"object","properties":{"summary":{"type":"object","properties":{"total_count":{"type":"integer","example":7}}}}},"shares":{"type":"object","properties":{"count":{"type":"integer","example":3}}},"attachments":{"type":"object","properties":{"data":{"type":"array","description":"Built from `mediaUrls`, `picture`, `full_picture` and `video`.","items":{"type":"object","properties":{"media":{"type":"object","description":"`{ image: { src } }` or `{ video: { src } }`."},"type":{"type":"string","description":"`photo`, `video`, `link` or `unknown` — guessed from the URL's file extension.","example":"photo"}}}}}},"author":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"username":{"type":"string"},"picture":{"type":"string"}}},"platform":{"type":"string","example":"instagram"},"sourceType":{"type":"string","example":"instagram-post"},"url":{"type":"string"},"views":{"type":"integer","example":0},"reach":{"type":"integer","example":0},"impressions":{"type":"integer","example":0}}}}}}}},"examples":{"failed":{"summary":"Platform not set up","value":{"success":false,"platform":"tiktok","action":"posts","error":"No sync configuration found for platform: tiktok","timestamp":1788000000000}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Refresh social data from a platform","description":"Calls the platform now for one kind of data, saves what comes back into the social store, and returns it in the unified feed shape.\n\nThe platform must be in the org's social sync settings (Instagram and WhatsApp can use the Facebook entry). The integration used is `configId`, else the one named in those settings, else `default`, and it must hold valid tokens. The account comes from `accountId`, else the settings (for Facebook and Instagram a saved `pageId/instagramId` pair is split into `pageId` and `instagramId`). **Every** query parameter is passed through to the platform call, so extra ones such as `limit` reach it; `feed` and `posts` default `limit` to 25.\n\n`action` picks the call: `feed` and `posts` → getFeed, `insights` and `analytics` → getInsights, `comments`, `messages`, `notifications`, `reactions`, `engagement`, `leads`, `ads`, `campaigns`, `followers`, `profile`; any other value calls `get<Action>` on the provider.\n\nEach returned item that has an `id` or `name` is saved as a `social_activity` with `platform`, `sourceType` (`<platform>-<action>`), `sourceId` (the item's id) and `timestamp` (the time of the refresh, unless the item has its own) added to the platform's fields. An item whose `sourceId` is already stored is skipped — the stored copy is not updated.\n\n#### Signature\n\n```http\nGET /sync/social-activities/refresh/{platform}/{action} (platform: string, action: string, configId?: string, accountId?: string, limit?: integer) -> Always HTTP 200 — check `success`. On success, the saved items in unified feed form; on failure, the reason.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A write on `GET`, and a live platform call that counts against its rate limits — do not put it behind a prefetchable link.\n- Failures (no sync settings, no configuration for the platform, integration missing or without valid tokens, the platform refusing the call) come back as `success: false`, not as an HTTP error.\n- Whatever the action, the response is shaped as a feed of posts — messages or insights are squeezed into the post layout.\n- Items saved here carry no `standardActivityType`, so the typed feeds (`/messages`, `/comments`, `/posts`, …) do not show them; they appear in `/platforms`, `/summary/platforms` and search.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/social-activities/posts`","tags":["CRM · Social"]}},"/crm/chat-message/get/{id}":{"get":{"operationId":"CRMController_getChatMessages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Chat message id. Omit to list all.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Chat messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/chat-message/get/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Chat"],"summary":"Get chat messages","description":"The caller's chat messages. Supply `id` for one; omit the segment to list them all.\n\n#### Signature\n\n```http\nGET /crm/chat-message/get/{id} (id: string) -> Chat messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/chat-message/create`"}},"/crm/chat-message/delete/{id}":{"delete":{"operationId":"CRMController_deleteChatMessage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","in":"path","required":true,"description":"Chat message id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"customer does not match — The record belongs to a different customer than the caller.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"customer does not match","path":"/crm/chat-message/delete/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/chat-message/delete/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Chat"],"summary":"Delete a chat message","description":"Deletes one of the caller's chat messages.\n\n#### Signature\n\n```http\nDELETE /crm/chat-message/delete/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n| `400` | CUSTOMER_MISMATCH | customer does not match | The record belongs to a different customer than the caller. | You can only act on your own messages. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/chat-message/update`"}},"/crm/chat-message/create":{"post":{"operationId":"CRMController_createChatMessage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created chat message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/chat-message/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Chat"],"summary":"Create a chat message","description":"Posts a new chat message on behalf of the caller.\n\n#### Signature\n\n```http\nPOST /crm/chat-message/create (body) -> The created chat message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/chat-message/update`","requestBody":{"description":"The message to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"data":{"type":"object","additionalProperties":true,"description":"Message fields."}}},"example":{"data":{"body":"Hello, I have a question about my order","channel":"chat"}}}}}}},"/crm/chat-message/update":{"post":{"operationId":"CRMController_updateChatMesssage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated chat message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"customer does not match — The record belongs to a different customer than the caller.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"customer does not match","path":"/crm/chat-message/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/chat-message/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Chat"],"summary":"Update a chat message","description":"Updates an existing chat message belonging to the caller.\n\n#### Signature\n\n```http\nPOST /crm/chat-message/update (body) -> The updated chat message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n| `400` | CUSTOMER_MISMATCH | customer does not match | The record belongs to a different customer than the caller. | You can only act on your own messages. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/chat-message/delete/{id}`","requestBody":{"description":"The message to update. Include its `sk`.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","description":"The message to update."},"data":{"type":"object","additionalProperties":true}}},"example":{"sk":"66f1a2b3c4d5e6f708192a3b","data":{"body":"Corrected text"}}}}}}},"/crm/flexdata/get/{id}":{"get":{"operationId":"CRMController_getFlexData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"query","required":false,"in":"query","schema":{"type":"string"},"description":"Filter expression.","example":"type=preference"},{"name":"id","in":"path","required":true,"description":"Record id. Omit to query.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The matching flex data","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/flexdata/get/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM"],"summary":"Get flex data","description":"Reads flexible, schema-less records scoped to the caller. Supply `id` for one, or a `query` to filter.\n\n#### Signature\n\n```http\nGET /crm/flexdata/get/{id} (id: string, query?: string) -> The matching flex data\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`."}},"/crm/inbox/conversations":{"get":{"operationId":"CRMController_inboxDistinctConversations","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"groupBy","in":"query","required":false,"description":"How messages are grouped into threads.","schema":{"type":"string","default":"contact_channel"},"example":"contact_channel"}],"responses":{"200":{"description":"One entry per conversation","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/conversations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"List inbox conversations","description":"Groups the caller's messages into conversations and returns one entry per thread — the list view of an inbox.\n\n`groupBy` decides what counts as a conversation. The default, `contact_channel`, treats one contact on one channel as a thread, so the same person's email and SMS appear separately.\n\nCovers `email`, `sms` and `chat` only. Social DMs are in `social_activity` and do not appear here.\n\n#### Signature\n\n```http\nGET /crm/inbox/conversations (groupBy?: string) -> One entry per conversation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The `parties` value on each conversation is what the thread endpoints take.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/inbox/conversations/{parties}`\n- `GET /crm/inbox/messages`"}},"/crm/inbox/conversations/{parties}":{"get":{"operationId":"CRMController_inboxGetConversationMessages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"parties","required":true,"in":"path","schema":{"type":"string"},"description":"Conversation key identifying the participants, as returned by `GET /crm/inbox/conversations`.","example":"ada@example.com|support@acme.com"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one channel — `email`, `sms`, `chat`.","example":"email"}],"responses":{"200":{"description":"The thread's messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A message (`DataType.message`) — email, SMS or chat.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"channel":{"type":"string","description":"How it travelled — `email`, `sms`, `chat`.","example":"email"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"from":{"type":"string","example":"ada@example.com"},"to":{"type":"string","example":"support@acme.com"},"subject":{"type":"string","example":"Where is my order?"},"body":{"type":"string"},"status":{"type":"string","description":"Read state or delivery state, depending on direction.","example":"unread"},"parties":{"type":"string","description":"Conversation key — the participants, used to group a thread.","example":"ada@example.com|support@acme.com"}}}}}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/conversations/{parties}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Get messages in a conversation","description":"Every message in one thread, in order. Narrow with `channel` when a contact has been reached on more than one.\n\n#### Signature\n\n```http\nGET /crm/inbox/conversations/{parties} (parties: string, channel?: string) -> The thread's messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/inbox/thread/{parties}`"}},"/crm/inbox/messages":{"get":{"operationId":"CRMController_inboxGetMessages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"l","required":false,"in":"query","schema":{"type":"string"},"description":"Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth."},{"name":"en","required":false,"in":"query","schema":{"type":"boolean"},"description":"Resolve linked records inline instead of returning bare references.","example":true},{"name":"p","in":"query","required":false,"description":"Page number, 1-based.","schema":{"type":"integer","default":1},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans.","schema":{"type":"integer","default":50},"example":50},{"name":"s","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string","default":"createdate"},"example":"createdate"},{"name":"st","in":"query","required":false,"description":"Sort direction.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"example":"desc"}],"responses":{"200":{"description":"A page of messages","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A message (`DataType.message`) — email, SMS or chat.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"channel":{"type":"string","description":"How it travelled — `email`, `sms`, `chat`.","example":"email"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"from":{"type":"string","example":"ada@example.com"},"to":{"type":"string","example":"support@acme.com"},"subject":{"type":"string","example":"Where is my order?"},"body":{"type":"string"},"status":{"type":"string","description":"Read state or delivery state, depending on direction.","example":"unread"},"parties":{"type":"string","description":"Conversation key — the participants, used to group a thread.","example":"ada@example.com|support@acme.com"}}}}}},"total":{"type":"integer","example":214}}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/messages","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"List inbox messages","description":"A flat, paged list of the caller's messages, newest first by default. Use the conversation endpoints when you want them threaded.\n\n#### Signature\n\n```http\nGET /crm/inbox/messages (p?: integer, ps?: integer, l?: string, s?: string, st?: string, en?: boolean) -> A page of messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/inbox/messages/{id}`"}},"/crm/inbox/messages/{id}":{"get":{"operationId":"CRMController_inboxGetMessage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Message id.","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"en","required":false,"in":"query","schema":{"type":"boolean"},"description":"Resolve linked records inline instead of returning bare references.","example":true}],"responses":{"200":{"description":"The message","content":{"application/json":{"schema":{"type":"object","description":"A message (`DataType.message`) — email, SMS or chat.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"channel":{"type":"string","description":"How it travelled — `email`, `sms`, `chat`.","example":"email"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"from":{"type":"string","example":"ada@example.com"},"to":{"type":"string","example":"support@acme.com"},"subject":{"type":"string","example":"Where is my order?"},"body":{"type":"string"},"status":{"type":"string","description":"Read state or delivery state, depending on direction.","example":"unread"},"parties":{"type":"string","description":"Conversation key — the participants, used to group a thread.","example":"ada@example.com|support@acme.com"}}}}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/messages/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"message not found — No message has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"message not found","path":"/crm/inbox/messages/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Get an inbox message","description":"Fetches one message. Pass `en=true` to resolve its linked records — the contact, the related order — inline.\n\n#### Signature\n\n```http\nGET /crm/inbox/messages/{id} (id: string, en?: boolean) -> The message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n| `404` | MESSAGE_NOT_FOUND | message not found | No message has that id. | Check the id with `GET /crm/inbox/messages`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/inbox/update-status/{messageId}/{status}`"}},"/crm/inbox/delete/{id}":{"delete":{"operationId":"CRMController_inboxDeleteMessage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","in":"path","required":true,"description":"Message id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"customer does not match — The record belongs to a different customer than the caller.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"customer does not match","path":"/crm/inbox/delete/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/delete/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"message not found — No message has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"message not found","path":"/crm/inbox/delete/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Delete an inbox message","description":"Deletes one message belonging to the caller. Deleting a whole thread is a separate endpoint.\n\n#### Signature\n\n```http\nDELETE /crm/inbox/delete/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n| `400` | CUSTOMER_MISMATCH | customer does not match | The record belongs to a different customer than the caller. | You can only act on your own messages. |\n| `404` | MESSAGE_NOT_FOUND | message not found | No message has that id. | Check the id with `GET /crm/inbox/messages`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/inbox/thread/{parties}`"}},"/crm/inbox/thread/{parties}":{"delete":{"operationId":"CRMController_inboxDeleteThread","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"channel","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one channel — `email`, `sms`, `chat`.","example":"email"},{"name":"parties","in":"path","required":true,"description":"Conversation key identifying the participants, as returned by `GET /crm/inbox/conversations`.","schema":{"type":"string"},"example":"ada@example.com|support@acme.com"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/thread/{parties}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Delete a conversation","description":"Deletes every message in a thread. Pass `channel` to remove only one channel's messages, leaving the contact's other conversations intact.\n\nThis removes the whole history with that contact — there is no undo.\n\n#### Signature\n\n```http\nDELETE /crm/inbox/thread/{parties} (parties: string, channel?: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Omitting `channel` deletes the thread across every channel with that contact.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/inbox/delete/{id}`"}},"/crm/inbox/update":{"post":{"operationId":"CRMController_inboxUpdateMessage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"send","required":false,"in":"query","schema":{"type":"boolean","default":false},"description":"`true` dispatches the message as well as saving it.","example":true}],"responses":{"201":{"description":"The saved message","content":{"application/json":{"schema":{"type":"object","description":"A message (`DataType.message`) — email, SMS or chat.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"channel":{"type":"string","description":"How it travelled — `email`, `sms`, `chat`.","example":"email"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"from":{"type":"string","example":"ada@example.com"},"to":{"type":"string","example":"support@acme.com"},"subject":{"type":"string","example":"Where is my order?"},"body":{"type":"string"},"status":{"type":"string","description":"Read state or delivery state, depending on direction.","example":"unread"},"parties":{"type":"string","description":"Conversation key — the participants, used to group a thread.","example":"ada@example.com|support@acme.com"}}}}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Message was not saved — an identical message already exists. Change the message, or open the existing one to send it. — An identical payload has already been saved — typically a double-clicked send.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Message was not saved — an identical message already exists. Change the message, or open the existing one to send it.","path":"/crm/inbox/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Update or send a message","description":"Saves a message, and **sends it when `send=true`** — the same endpoint covers saving a draft and dispatching it.\n\nA duplicate guard rejects an identical payload posted twice with a `409`, so a double-clicked send does not mail the customer twice. The error says plainly what happened rather than surfacing as a generic failure.\n\n#### Signature\n\n```http\nPOST /crm/inbox/update (send?: boolean, body) -> The saved message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sending is driven by the `send` query parameter, not the body — it is easy to save when you meant to send.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n| `409` | DUPLICATE_MESSAGE | Message was not saved — an identical message already exists. Change the message, or open the existing one to send it. | An identical payload has already been saved — typically a double-clicked send. | Treat this as success for a retry. The message already exists; open it rather than resending. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/send-template`","requestBody":{"description":"The message to save or send.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","description":"Existing message to update. Omit to create."},"data":{"type":"object","additionalProperties":true,"description":"Message fields — channel, to, subject, body."}}},"examples":{"draft":{"summary":"Save a draft","value":{"data":{"channel":"email","to":"ada@example.com","subject":"Your order","body":"Hello…"}}},"send":{"summary":"Save and send","description":"Set `?send=true` on the query string.","value":{"data":{"channel":"email","to":"ada@example.com","subject":"Your order","body":"Hello…"}}}}}}}}},"/crm/inbox/update-status/{messageId}/{status}":{"post":{"operationId":"CRMController_inboxUpdateMessageStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"messageId","in":"path","required":true,"description":"Message id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"status","in":"path","required":true,"description":"New status, e.g. `read`, `unread`, `archived`.","schema":{"type":"string"},"example":"read"}],"responses":{"201":{"description":"The updated message","content":{"application/json":{"schema":{"type":"object","description":"A message (`DataType.message`) — email, SMS or chat.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"channel":{"type":"string","description":"How it travelled — `email`, `sms`, `chat`.","example":"email"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"from":{"type":"string","example":"ada@example.com"},"to":{"type":"string","example":"support@acme.com"},"subject":{"type":"string","example":"Where is my order?"},"body":{"type":"string"},"status":{"type":"string","description":"Read state or delivery state, depending on direction.","example":"unread"},"parties":{"type":"string","description":"Conversation key — the participants, used to group a thread.","example":"ada@example.com|support@acme.com"}}}}}}}},"400":{"description":"customer does not match — The record belongs to a different customer than the caller.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"customer does not match","path":"/crm/inbox/update-status/{messageId}/{status}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/update-status/{messageId}/{status}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"message not found — No message has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"message not found","path":"/crm/inbox/update-status/{messageId}/{status}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Set a message status","description":"Changes a message's status — marking it read, archived or handled. Both values are path segments rather than a body.\n\n#### Signature\n\n```http\nPOST /crm/inbox/update-status/{messageId}/{status} (messageId: string, status: string) -> The updated message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n| `400` | CUSTOMER_MISMATCH | customer does not match | The record belongs to a different customer than the caller. | You can only act on your own messages. |\n| `404` | MESSAGE_NOT_FOUND | message not found | No message has that id. | Check the id with `GET /crm/inbox/messages`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/inbox/messages/{id}`"}},"/crm/send-template":{"post":{"operationId":"CRMController_sendTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The sent message","content":{"application/json":{"schema":{"type":"object","description":"A message (`DataType.message`) — email, SMS or chat.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"channel":{"type":"string","description":"How it travelled — `email`, `sms`, `chat`.","example":"email"},"direction":{"type":"string","enum":["inbound","outbound"],"example":"inbound"},"from":{"type":"string","example":"ada@example.com"},"to":{"type":"string","example":"support@acme.com"},"subject":{"type":"string","example":"Where is my order?"},"body":{"type":"string"},"status":{"type":"string","description":"Read state or delivery state, depending on direction.","example":"unread"},"parties":{"type":"string","description":"Conversation key — the participants, used to group a thread.","example":"ada@example.com|support@acme.com"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Templates"],"summary":"Send a templated message","description":"Renders a message template and sends it. This is the path for transactional mail that should look like every other message from the org — the template chain resolves org override, then shared-org, then the factory default.\n\nUse `POST /crm/test-template` first to see the rendered output without sending.\n\n#### Signature\n\n```http\nPOST /crm/send-template (body) -> The sent message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends on every call — there is no duplicate guard here, unlike `POST /crm/inbox/update`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/test-template`","requestBody":{"description":"Which template, to whom, with what data.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"template":{"type":"string","description":"Template name.","example":"order-confirmation"},"to":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Recipients.","example":"ada@example.com"},"channel":{"type":"string","enum":["email","sms"],"example":"email"},"data":{"type":"object","additionalProperties":true,"description":"Values interpolated into the template."}}},"example":{"template":"order-confirmation","to":"ada@example.com","channel":"email","data":{"orderNumber":"A7K2M9QX4"}}}}}}},"/crm/test-template":{"post":{"operationId":"CRMController_testTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The rendered template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Templates"],"summary":"Test a message template","description":"Renders a template with sample data and returns the result **without sending it**. The safe way to check wording and interpolation before mailing customers.\n\n#### Signature\n\n```http\nPOST /crm/test-template (body) -> The rendered template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Nothing is sent and nothing is stored.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/send-template`","requestBody":{"description":"The template and the data to render it with.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"template":{"type":"string","example":"order-confirmation"},"data":{"type":"object","additionalProperties":true,"description":"Values to interpolate."}}},"example":{"template":"order-confirmation","data":{"orderNumber":"A7K2M9QX4"}}}}}}},"/crm/inbox/notifications/{id}":{"get":{"operationId":"CRMController_inboxGetNotifications","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Notification id. Omit to list all.","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"en","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Notifications","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/notifications/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Get inbox notifications","description":"The caller's notifications. Supply `id` for one; omit the segment to list them all.\n\n#### Signature\n\n```http\nGET /crm/inbox/notifications/{id} (id: string) -> Notifications\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/inbox/save-push-token`"}},"/crm/inbox/save-push-token":{"post":{"operationId":"CRMController_inboxSaveToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved token record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"customer is required — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"customer is required","path":"/crm/inbox/save-push-token","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Inbox"],"summary":"Save a push notification token","description":"Registers a device token so the caller can receive push notifications. Call it on every app launch — tokens are rotated by the platforms and a stale one silently stops delivering.\n\n#### Signature\n\n```http\nPOST /crm/inbox/save-push-token (body) -> The saved token record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | customer is required | No signed-in customer or user could be resolved. | Sign in — these routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/inbox/notifications/{id}`","requestBody":{"description":"The device token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"token":{"type":"string","description":"Device push token.","example":"fcm_dGhpcyBpcyBhIHRva2Vu"},"platform":{"type":"string","enum":["ios","android","web"],"example":"ios"}}},"example":{"token":"fcm_dGhpcyBpcyBhIHRva2Vu","platform":"ios"}}}}}},"/crm/place/near-by":{"get":{"operationId":"CRMController_getNearbyPlaces","parameters":[{"name":"lat","required":true,"in":"query","schema":{"type":"number"},"description":"Latitude.","example":40.7128},{"name":"lng","required":true,"in":"query","schema":{"type":"number"},"description":"Longitude.","example":-74.006},{"name":"radius","required":true,"in":"query","schema":{"type":"number"},"description":"Search radius in metres.","example":1500},{"name":"type","required":true,"in":"query","schema":{"type":"string"},"description":"Place type to search for.","example":"restaurant"}],"responses":{"200":{"description":"Nearby places","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM"],"summary":"Find nearby places","description":"Looks up places near a point, backed by a maps provider.\n\n**This route reads no `orgid` header** and is not scoped to an organization — it is a plain proxy to the places lookup.\n\n#### Signature\n\n```http\nGET /crm/place/near-by (lat?: number, lng?: number, radius?: number, type?: string) -> Nearby places\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Backed by a billable maps provider — do not call it per keystroke.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/crm/send-invitation":{"post":{"operationId":"CRMController_sendInvitation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The invitation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Templates"],"summary":"Send an invitation","description":"Sends an invitation to join the organization. The invitee receives a link to accept.\n\n#### Signature\n\n```http\nPOST /crm/send-invitation (body) -> The invitation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends every time — re-inviting an existing member mails them again.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"Who to invite.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"role":{"type":"string","description":"Role to grant on acceptance.","example":"User"},"message":{"type":"string","description":"Personal note included in the invitation."}}},"example":{"email":"ada@example.com","role":"User"}}}}}},"/crm/workflows/benefit-application":{"post":{"operationId":"CRMController_createBenefitWorkflow","summary":"Create a benefit application workflow","description":"Scaffolds a standard benefit-application workflow from the built-in template. Customise its stages afterwards through the workflow API.\n\n#### Signature\n\n```http\nPOST /crm/workflows/benefit-application (body) -> The created workflow\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/workflows/benefit-application/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The created workflow","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Benefits"],"requestBody":{"description":"Workflow name.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Unique workflow name. Defaults to the standard benefit-application name.","example":"benefit-application"}}},"example":{"name":"benefit-application"}}}}}},"/crm/workflows/benefit-application/{name}":{"get":{"operationId":"CRMController_getBenefitWorkflow","summary":"Get the benefit application workflow","description":"Fetches a benefit-application workflow by name, or omit the segment to find it by the datatype it is bound to — the reliable way to ask which pipeline benefit applications run on.\n\n#### Signature\n\n```http\nGET /crm/workflows/benefit-application/{name} (name: string) -> The workflow definition\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/workflows/benefit-application`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Workflow name. Omit to find by owner datatype.","schema":{"type":"string"},"example":"benefit-application"}],"responses":{"200":{"description":"The workflow definition","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Benefits"]}},"/crm/benefit-enrollments":{"get":{"operationId":"CRMController_getEnrollments","summary":"List benefit enrolments","description":"Lists benefit enrolments with optional filters. The operator queue for reviewing applications — filter on `status` for those awaiting a decision.\n\n#### Signature\n\n```http\nGET /crm/benefit-enrollments (customerId?: string, benefit?: string, status?: string) -> Matching enrolments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/benefit-enrollments/{enrollmentId}/action`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"customerId","required":false,"in":"query","schema":{"type":"string"},"description":"Filter to one customer.","example":"cus_4821"},{"name":"benefit","required":false,"in":"query","schema":{"type":"string"},"description":"Filter to one benefit.","example":"health-plan"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by enrolment status.","example":"pending"}],"responses":{"200":{"description":"Matching enrolments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Benefits"]}},"/crm/benefit-enrollments/{enrollmentId}/action":{"post":{"operationId":"CRMController_updateEnrollmentStatus","summary":"Review, approve or reject an enrolment","description":"Acts on a benefit enrolment. `action` decides what happens: `review` marks it as being examined, `approve` accepts it, `reject` declines it.\n\nNotes are recorded against the decision and are what an applicant is shown when rejected, so write them for that audience.\n\n#### Signature\n\n```http\nPOST /crm/benefit-enrollments/{enrollmentId}/action (enrollmentId: string, body) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/benefit-enrollments/{enrollmentId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"enrollmentId","required":true,"in":"path","description":"Enrolment id.","schema":{"type":"string"},"example":"ENR-4821"}],"responses":{"201":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Benefits"],"requestBody":{"description":"The decision.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["review","approve","reject"],"example":"approve"},"notes":{"type":"string","description":"Recorded with the decision.","example":"Eligibility confirmed against payroll records"}}},"examples":{"approve":{"summary":"Approve","value":{"action":"approve","notes":"Eligibility confirmed"}},"reject":{"summary":"Reject with a reason","value":{"action":"reject","notes":"Applicant does not meet the service-length requirement"}}}}}}}},"/crm/benefit-enrollments/{enrollmentId}":{"delete":{"operationId":"CRMController_deleteEnrollment","summary":"Delete a benefit enrolment","description":"Deletes an enrolment **and all its associated data**, including the submitted application. This is a hard delete with nothing to restore — reject the enrolment instead when you need to keep the record of what was applied for.\n\n#### Signature\n\n```http\nDELETE /crm/benefit-enrollments/{enrollmentId} (enrollmentId: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Removes associated application data too. Prefer `reject` where an audit trail matters.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/benefit-enrollments/{enrollmentId}/action`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"enrollmentId","required":true,"in":"path","description":"Enrolment id.","schema":{"type":"string"},"example":"ENR-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Benefits"]}},"/crm/customer-data/{customerId}":{"get":{"operationId":"CRMController_getCustomerData","summary":"Get complete customer data","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"customerId","required":true,"in":"path","description":"Customer `sk`, email address, or username.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"The customer and every linked record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Customer not found — The identifier does not resolve to a customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Customer not found","path":"/crm/customer-data/{customerId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Customers"],"description":"Returns a customer together with **every linked record** — addresses, phone numbers, and the other sub-records attached to them — in one response.\n\nThe identifier is flexible: a customer `sk`, an email address or a username all resolve, so a support tool can look someone up by whatever it has.\n\n#### Signature\n\n```http\nGET /crm/customer-data/{customerId} (customerId: string) -> The customer and every linked record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CUSTOMER_NOT_FOUND | Customer not found | The identifier does not resolve to a customer. | Try the email address or `sk` — all three forms are accepted. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/customer-data/{customerId}`"},"post":{"operationId":"CRMController_addCustomerData","summary":"Add linked data to a customer","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"customerId","required":true,"in":"path","description":"Customer `sk`, email address, or username.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"201":{"description":"The created linked record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Customer not found — The identifier does not resolve.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Customer not found","path":"/crm/customer-data/{customerId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Customers"],"description":"Attaches a sub-record to a customer — an address, a phone number, or any other linked datatype. `datatype` names what kind of record it is and `data` carries its fields.\n\nAs with the read, the customer can be identified by `sk`, email or username.\n\n#### Signature\n\n```http\nPOST /crm/customer-data/{customerId} (customerId: string, body) -> The created linked record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Notes\n\n- `datatype` is not validated against a list — an unrecognised value creates a record nothing else reads.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | CUSTOMER_NOT_FOUND | Customer not found | The identifier does not resolve. | Check the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/customer-data/{customerId}`","requestBody":{"description":"The linked record to add.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["datatype","data"],"properties":{"datatype":{"type":"string","description":"What kind of record this is.","example":"address"},"data":{"type":"object","additionalProperties":true,"description":"The record's fields."}}},"examples":{"address":{"summary":"Add an address","value":{"datatype":"address","data":{"line1":"12 Ada Way","city":"London","postcode":"E1 6AN","country":"GB"}}},"phone":{"summary":"Add a phone number","value":{"datatype":"phone","data":{"number":"+442071234567","label":"mobile"}}}}}}}}},"/crm/customer-activity/{email}/timeline":{"get":{"operationId":"CustomerActivityController_getTimeline","summary":"Get a customer activity timeline","description":"Everything a customer has done, in order — the behavioural history behind their record.\n\n#### Signature\n\n```http\nGET /crm/customer-activity/{email}/timeline (email: string, types?: string, from?: string, to?: string, limit?: string, page?: string) -> The activity timeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/customer-activity/{email}/summary`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Customer email.","example":"ada@example.com"},{"name":"types","required":false,"in":"query","description":"Comma-separated activity types.","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"ISO date — start of the range.","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"ISO date — end of the range.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"string"},"description":"Maximum rows."},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."}],"responses":{"200":{"description":"The activity timeline","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Activity"]}},"/crm/customer-activity/visitor/{visitorId}/timeline":{"get":{"operationId":"CustomerActivityController_getVisitorTimeline","summary":"Get an anonymous visitor timeline","description":"Activity for a visitor who has not identified themselves yet, tracked by visitor id. Calling `identify` links this history to a real customer.\n\n#### Signature\n\n```http\nGET /crm/customer-activity/visitor/{visitorId}/timeline (visitorId: string, limit?: string, page?: string) -> The visitor timeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/customer-activity/identify`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"visitorId","required":true,"in":"path","schema":{"type":"string"},"description":"Anonymous visitor id.","example":"vis_9k2m4h1p7q"},{"name":"limit","required":false,"in":"query","schema":{"type":"string"},"description":"Maximum rows."},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."}],"responses":{"200":{"description":"The visitor timeline","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Activity"]}},"/crm/customer-activity/{email}/summary":{"get":{"operationId":"CustomerActivityController_getSummary","summary":"Get a customer activity summary","description":"Aggregate view of a customer's behaviour rather than the raw event list — visit counts, recency and engagement.\n\n#### Signature\n\n```http\nGET /crm/customer-activity/{email}/summary (email: string) -> The activity summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/customer-activity/{email}/timeline`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Customer email.","example":"ada@example.com"}],"responses":{"200":{"description":"The activity summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Activity"]}},"/crm/customer-activity/identify":{"post":{"operationId":"CustomerActivityController_identifyVisitor","summary":"Identify a visitor","description":"Links an anonymous visitor's history to a known customer — what happens at sign-up or sign-in, so the browsing that led to the account is not lost.\n\nCall it as soon as identity is known; activity recorded before it stays attached to the visitor id.\n\n#### Signature\n\n```http\nPOST /crm/customer-activity/identify (body) -> The identification result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/customer-activity/record`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The visitor and who they turned out to be.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"visitorId":{"type":"string","example":"vis_9k2m4h1p7q"},"email":{"type":"string","example":"ada@example.com"}}},"example":{"visitorId":"vis_9k2m4h1p7q","email":"ada@example.com"}}}},"responses":{"201":{"description":"The identification result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Activity"]}},"/crm/customer-activity/record":{"post":{"operationId":"CustomerActivityController_recordEvent","summary":"Record customer activity","description":"Records an activity event against a customer or visitor — a page view, a search, a product view. The write behind the timeline.\n\n#### Signature\n\n```http\nPOST /crm/customer-activity/record (body) -> The recorded event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- High-volume by nature — batch where you can rather than calling per interaction.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/customer-activity/erase`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The recorded event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Activity"],"requestBody":{"description":"The event to record.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"visitorId":{"type":"string"},"email":{"type":"string"},"type":{"type":"string","example":"product_view"},"data":{"type":"object","additionalProperties":true}}},"example":{"visitorId":"vis_9k2m4h1p7q","type":"product_view","data":{"sku":"DRK-COLA-330"}}}}}}},"/crm/customer-activity/erase":{"post":{"operationId":"CustomerActivityController_eraseCustomer","summary":"Erase customer activity","description":"Deletes a customer's recorded activity — the data-subject erasure path for a deletion request.\n\nIrreversible by design: the point is that the history genuinely goes.\n\n#### Signature\n\n```http\nPOST /crm/customer-activity/erase (body) -> The erasure result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Irreversible. This erases activity only — the customer record itself is deleted through the user API.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/customer-activity/{email}/timeline`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Whose activity to erase.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"visitorId":{"type":"string"}}},"example":{"email":"ada@example.com"}}}},"responses":{"201":{"description":"The erasure result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Activity"]}},"/crm/customer-360":{"get":{"operationId":"Customer360Controller_get","summary":"Everything recent about a phone number or email","description":"Who it is (customers, leads) and their recent calls, messages, orders, invoices, tickets, reservations, AI employee work and website activity — computed on the server, ready to show. A phone number matches on its last 10 digits.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"phone","required":false,"in":"query","schema":{"type":"string"}},{"name":"email","required":false,"in":"query","schema":{"type":"string"}},{"name":"name","required":false,"in":"query","description":"A name: returns { matches } — the customers and leads to pick from.","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Customer 360"]}},"/crm/alert/sms/{id}":{"get":{"operationId":"CRMAlertController_setSMSAlert","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","in":"path","required":true,"description":"Alert id. Omit to list.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"SMS alerts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get SMS alerts","description":"Fetches one SMS alert configuration by id, or lists them when the segment is omitted.\n\n#### Signature\n\n```http\nGET /crm/alert/sms/{id} (id: string) -> SMS alerts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/alert/sms`","tags":["CRM · Alerts"]}},"/crm/alert/sms":{"post":{"operationId":"CRMAlertController_sendSMSAlert","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created alert","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create an SMS alert","description":"Creates an SMS alert — a message triggered by a condition rather than sent manually.\n\n#### Signature\n\n```http\nPOST /crm/alert/sms (body) -> The created alert\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- SMS costs money per message — check the trigger cannot fire in a loop.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/alert/sms/{id}`","tags":["CRM · Alerts"],"requestBody":{"description":"The alert to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"data":{"name":"Low stock alert","to":"+15551234567","trigger":"inventory.low"}}}}}}},"/crm/marketing/campaign-manager/list":{"post":{"operationId":"MarketingController_managerList","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ summary, data: Campaign[], total, page, pageSize, hasNext }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List campaigns (Campaign Manager)","description":"Totals across **every** campaign in the org (`summary`: counts per status, budget, spent and the other metrics, tracked/untracked counts, `statusOptions`) plus one page of enriched campaign cards. Each card carries its computed `status`/`statusLabel`, `channel` (ads | email | offline), `metrics` gathered from every source that reports on it (ad platform insights, linked social posts, the linked email broadcast, checkout attribution, manually entered actuals), `tracking` (which sources, or \"Not tracked\"), `failure` for a failed launch, `adsReadiness`, `blockedActions` and `testLaunch`. `status` and `search` (name, description, type, platforms) filter the page only; totals never change with them.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/list (body) -> { summary, data: Campaign[], total, page, pageSize, hasNext }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"page":{"type":"integer","default":1},"pageSize":{"type":"integer","default":24,"maximum":100},"status":{"type":"string","enum":["all","draft","scheduled","active","paused","completed","failed","cancelled"]},"search":{"type":"string"}}},"example":{"status":"active","pageSize":24}}}}}},"/crm/marketing/campaign-manager/social-feeds":{"get":{"operationId":"MarketingController_managerSocialFeeds","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Posts per platform, 1–50 (default 10)."}],"responses":{"200":{"description":"Platform feeds","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Social feeds by platform","description":"One column per social network: its connection `state` (connected | needs_reconnect | not_connected | no_account), a `message` and next `action` when it needs attention, and the latest synced posts.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaign-manager/social-feeds (limit?: integer) -> Platform feeds\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/create":{"post":{"operationId":"MarketingController_managerCreate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The new campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A campaign needs a name — `name` missing or blank (create, or an update that sends it).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A campaign needs a name","path":"/crm/marketing/campaign-manager/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"This campaign was just created — open it from Campaigns to keep editing it — The same create was sent twice in a row (double click / resend).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This campaign was just created — open it from Campaigns to keep editing it","path":"/crm/marketing/campaign-manager/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a campaign","description":"Creates a campaign in `draft`. `name` and `type` are required; `type` is one of `email`, `social`, `ppc`, `display`, `video`, `radio`, `billboard`, `newspaper`, `magazine`, `tv`, `website`. Editable fields: name, description, type, objective, budget (≥ 0), budgetType (daily | lifetime), dailyBudget, platforms, landingPage, utmParameters, tags, notes, location, timeSlots, frequency, duration, audienceIds, targetAudience, audiences, timezone, broadcastId (email campaigns), creatives and the Smart Builder state. The ad account is never set here — see `POST …/{id}/ad-account`.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/create (body) -> The new campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAME_REQUIRED | A campaign needs a name | `name` missing or blank (create, or an update that sends it). | — |\n| `409` | DUPLICATE_CREATE | This campaign was just created — open it from Campaigns to keep editing it | The same create was sent twice in a row (double click / resend). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Fall sale — Meta","type":"social","platforms":["facebook","instagram"],"budget":500,"budgetType":"lifetime"}}}}}},"/crm/marketing/campaign-manager/social-draft":{"post":{"operationId":"MarketingController_managerSocialDraft","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ id, platform, status: \"draft\", text, campaignId, openIn }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Platform must be one of: facebook, instagram, linkedin, twitter, tiktok, pinterest — Unknown platform.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Platform must be one of: facebook, instagram, linkedin, twitter, tiktok, pinterest","path":"/crm/marketing/campaign-manager/social-draft","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/social-draft","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Draft a social post for a campaign","description":"Creates a draft social post (a `message` with status `draft`, never sent) for one platform — `facebook`, `instagram`, `linkedin`, `twitter`, `tiktok`, `pinterest` — linked to the campaign when `campaignId` is given. It is reviewed and published from Social › Posts.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/social-draft (body) -> { id, platform, status: \"draft\", text, campaignId, openIn }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BAD_PLATFORM | Platform must be one of: facebook, instagram, linkedin, twitter, tiktok, pinterest | Unknown platform. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"campaignId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","linkedin","twitter","tiktok","pinterest"]},"text":{"type":"string"},"imageUrl":{"type":"string"}}},"example":{"campaignId":"66f1a2b3c4d5e6f708192a3b","platform":"instagram","text":"Fall sale starts Friday 🍂"}}}}}},"/crm/marketing/campaign-manager/ads-readiness":{"get":{"operationId":"MarketingController_managerAdsReadiness","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"campaign","required":false,"in":"query","schema":{"type":"string"},"description":"Narrow to this campaign: only its ad platforms, with its own ad account overrides applied.","example":"68413e97a20417481150d5ed"},{"name":"refresh","required":false,"in":"query","schema":{"type":"string"},"description":"`1` to skip the short cache.","example":"1"}],"responses":{"200":{"description":"`{ checkedAt, campaign, applies, ready, blockedReason, summary: { total, ready, attention }, platforms: [{ platform, label, connection, ready, status, adAccount, issues: [{ code, message, severity, fix }], supportsTestLaunch, guide }] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ready":false,"blockedReason":"Facebook, Instagram — No ad account is chosen for Facebook & Instagram (Meta).","summary":{"total":2,"ready":0,"attention":2},"platforms":[{"platform":"facebook","label":"Facebook","connection":"meta","ready":false,"status":"needs_attention","adAccount":null,"issues":[{"code":"no_ad_account_selected","severity":"blocker","message":"No ad account is chosen for Facebook & Instagram (Meta).","fix":{"label":"Choose ad account","target":{"app":"/social-manager","tab":"accounts","section":"meta"}}}]}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Check whether each ad platform can publish ads","description":"For every ad platform (`facebook`, `instagram`, `google`, `linkedin`, `tiktok`, `pinterest`, `twitter`), or for one campaign when `campaign` is given, the answer is **ready** or the list of issues in the way. Each issue has a `fix: { label, target }`, where `target` is either an in-app screen (`{ app: \"/social-manager\", tab: \"accounts\", section }`, the platform's \"Ad account\" settings) or a page on the platform itself (`{ url }`).\n\nWhat is checked, read-only on the platforms (nothing is created and nothing is spent):\n- the connection exists, is turned on, and has a token that has not expired;\n- the token carries the ads permission (Meta `ads_management` + `ads_read` from `/me/permissions`; Google `adwords` scope; LinkedIn `rw_ads`; Pinterest `ads:read`). TikTok needs a TikTok for Business (Marketing API) advertiser authorization, and X needs X Ads API access; posting-only connections are reported as `ads_api_unavailable`;\n- an ad account is chosen: the campaign's own override, otherwise the connection default (`adAccountIds` on the connection);\n- that account is one the login can use and is fully ready: active, with a payment method where the API reports it (Meta `account_status`, `disable_reason`, `funding_source_details`; Google customer status and an approved billing setup);\n- the identity ads run as: a Facebook Page for Facebook, an Instagram professional account linked to a Page for Instagram.\n\nResults are cached per organization for about a minute; `refresh=1` checks again.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaign-manager/ads-readiness (campaign?: string, refresh?: string) -> `{ checkedAt, campaign, applies, ready, blockedReason, summary: { total, ready, attention }, platforms: [{ platform, label, connection, ready, status, adAccount, issues: [{ code, message, severity, fix }], supportsTestLaunch, guide }] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Issue codes: `not_connected`, `connection_inactive`, `token_expired`, `token_invalid`, `missing_ads_permission`, `ads_api_unavailable`, `no_ad_account_selected`, `no_usable_ad_account`, `ad_account_not_found`, `ad_account_not_ready`, `no_page`, `no_instagram_account`, `platform_integration_outdated`, `check_failed`.\n- Also mounted as `GET /crm/marketing/campaign-manager/ads-readiness/{campaignId}`.\n- Campaign cards from `POST /crm/marketing/campaign-manager/list` carry the same result as `adsReadiness`, plus `blockedActions` (`launch`, `retry`, `schedule` → the reason) while a targeted platform is not ready.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaign-manager/ad-accounts`\n- `POST /crm/marketing/campaign-manager/{id}/launch`","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/ads-readiness/{campaignId}":{"get":{"operationId":"MarketingController_managerAdsReadinessFor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id."},{"name":"refresh","required":false,"in":"query","schema":{"type":"string","enum":["1","true"]},"description":"Re-check the platforms instead of the short cache."}],"responses":{"200":{"description":"Readiness per platform","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Ads readiness for one campaign","description":"Same as `GET …/ads-readiness?campaign=` — only the platforms this campaign targets.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaign-manager/ads-readiness/{campaignId} (campaignId: string, refresh?: string) -> Readiness per platform\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaign-manager/ads-readiness`","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/ad-accounts":{"get":{"operationId":"MarketingController_managerAdAccounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"A platform (`facebook`, `instagram`, `google`, `linkedin`, `tiktok`, `pinterest`, `twitter`) or connection key (`meta`). Facebook and Instagram share the Meta connection.","example":"meta"},{"name":"refresh","required":false,"in":"query","schema":{"type":"string"},"description":"`1` to skip the short cache.","example":"1"}],"responses":{"200":{"description":"`{ connection, label, platforms, connected, issues, canList, accounts: [{ id, name, currency, status, selectable, reasons, paymentMethod, business, fixUrl }], usableCount, selected, manualEntry, guide, checkedAt }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List the ad accounts a platform connection can use","description":"The ad accounts the connected login can see on a platform, each marked `selectable` or not with `reasons` in plain words (inactive or disabled, no payment method, limited API access…) and a `fixUrl` on the platform where there is one. `selected` is the connection's current default. When nothing is usable, `guide` holds the platform's own steps to create one (official link included). `manualEntry` is true where an id can be typed in and verified (Meta). Without `platform`, every connection is returned as `{ connections: [...] }`.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaign-manager/ad-accounts (platform?: string, refresh?: string) -> `{ connection, label, platforms, connected, issues, canList, accounts: [{ id, name, currency, status, selectable, reasons, paymentMethod, business, fixUrl }], usableCount, selected, manualEntry, guide, checkedAt }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Also mounted as `GET /crm/marketing/campaign-manager/ad-accounts/{platform}`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/campaign-manager/ad-accounts/default`","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/ad-accounts/{platform}":{"get":{"operationId":"MarketingController_managerAdAccountsFor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":true,"in":"path","schema":{"type":"string"},"description":"Ad platform key.","example":"facebook"},{"name":"refresh","required":false,"in":"query","schema":{"type":"string","enum":["1","true"]}}],"responses":{"200":{"description":"Ad accounts, each marked usable or not with the reason","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Ad accounts for one platform","description":"Same as `GET …/ad-accounts?platform=`.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaign-manager/ad-accounts/{platform} (platform: string, refresh?: string) -> Ad accounts, each marked usable or not with the reason\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaign-manager/ad-accounts`","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/ad-accounts/default":{"post":{"operationId":"MarketingController_managerSetDefaultAdAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The connection's ad accounts, as `GET …/ad-accounts`, with the new `selected`.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"… the connected login has no access to ad account … — The id is not one this login can use.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"… the connected login has no access to ad account …","path":"/crm/marketing/campaign-manager/ad-accounts/default","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Choose a platform connection's ad account","description":"Saves the ad account that campaigns on this platform launch from (stored on the connection as `adAccountIds`; also `adsAccountId` for Google and `advertiserId` for TikTok, which those integrations read). The account is checked against the platform again first: only one the login can use **and** that is fully ready is accepted. `accountId: null` clears it.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/ad-accounts/default (body) -> The connection's ad accounts, as `GET …/ad-accounts`, with the new `selected`.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The error body carries `reason` (the code above) next to the message.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | ad_account_not_found | … the connected login has no access to ad account … | The id is not one this login can use. | Pick one from the list, or give the login access on the platform and refresh. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaign-manager/ad-accounts`","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","example":"facebook"},"accountId":{"type":"string","nullable":true,"example":"act_24661883323447662"}},"required":["platform"]},"example":{"platform":"facebook","accountId":"act_24661883323447662"}}}}}},"/crm/marketing/campaign-manager/{id}":{"get":{"operationId":"MarketingController_managerGet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The enriched campaign card (same shape as the list rows)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a campaign card","description":"#### Signature\n\n```http\nGET /crm/marketing/campaign-manager/{id} (id: string) -> The enriched campaign card (same shape as the list rows)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]},"delete":{"operationId":"MarketingController_managerDelete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"{ deleted: true, id }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a campaign","description":"Cancels its schedules, pauses it on its ad platforms if it is live (the platform campaign and its history are kept in the ad account), removes any paused test campaign, then deletes the record.\n\n#### Signature\n\n```http\nDELETE /crm/marketing/campaign-manager/{id} (id: string) -> { deleted: true, id }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/update":{"post":{"operationId":"MarketingController_managerUpdate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A campaign needs a name — `name` missing or blank (create, or an update that sends it).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A campaign needs a name","path":"/crm/marketing/campaign-manager/{id}/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update a campaign","description":"Changes any editable field (see create). On a live ad campaign a new name or budget is also pushed to its platforms; a platform refusal is logged and the local change is kept.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/update (id: string, body) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAME_REQUIRED | A campaign needs a name | `name` missing or blank (create, or an update that sends it). | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"budget":750}}}}}},"/crm/marketing/campaign-manager/{id}/launch":{"post":{"operationId":"MarketingController_managerLaunch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This campaign can't launch yet. Facebook, Instagram — No ad account is chosen for Facebook & Instagram (Meta). — A targeted ad platform is not connected, lacks the ads permission, or has no usable ad account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This campaign can't launch yet. Facebook, Instagram — No ad account is chosen for Facebook & Instagram (Meta).","path":"/crm/marketing/campaign-manager/{id}/launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Launch a campaign","description":"Launches through the campaign's channel. An **ad** campaign is refused up front while any platform it targets is not ready: nothing is sent to any platform and the campaign keeps its status. `POST …/{id}/schedule` applies the same check before booking a launch, and a scheduled launch that comes due while a platform is not ready is marked failed with the reason.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/launch (id: string) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | ads_not_ready | This campaign can't launch yet. Facebook, Instagram — No ad account is chosen for Facebook & Instagram (Meta). | A targeted ad platform is not connected, lacks the ads permission, or has no usable ad account. | The body carries `readiness` (as `GET …/ads-readiness`) — follow each issue's `fix`, then launch again. |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaign-manager/ads-readiness`","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/pause":{"post":{"operationId":"MarketingController_managerPause","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only an active campaign can be paused (this one is <status>) — The campaign is not active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only an active campaign can be paused (this one is <status>)","path":"/crm/marketing/campaign-manager/{id}/pause","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/pause","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pause a campaign","description":"Only an active campaign. Pauses it on every ad platform it is live on (results kept in `platformResults`) and, for an email campaign, cancels the linked broadcast if it has not gone out.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/pause (id: string) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_ACTIVE | Only an active campaign can be paused (this one is <status>) | The campaign is not active. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/resume":{"post":{"operationId":"MarketingController_managerResume","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a paused campaign can be resumed (this one is <status>) — The campaign is not paused.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a paused campaign can be resumed (this one is <status>)","path":"/crm/marketing/campaign-manager/{id}/resume","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/resume","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Resume a paused campaign","description":"Only a paused campaign. Re-activates it on its ad platforms and sets it active.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/resume (id: string) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_PAUSED | Only a paused campaign can be resumed (this one is <status>) | The campaign is not paused. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/complete":{"post":{"operationId":"MarketingController_managerComplete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a running or paused campaign can be ended — Any other status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a running or paused campaign can be ended","path":"/crm/marketing/campaign-manager/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"End a campaign","description":"Only an active or paused campaign. Pauses it on its ad platforms (if active), cancels any pending schedule and marks it completed.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/complete (id: string) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_RUNNING | Only a running or paused campaign can be ended | Any other status. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/schedule":{"post":{"operationId":"MarketingController_managerSchedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A <status> campaign cannot be scheduled — Status is not draft, scheduled or failed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A <status> campaign cannot be scheduled","path":"/crm/marketing/campaign-manager/{id}/schedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/schedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This campaign can't be scheduled yet. … — A targeted ad platform is not ready; body carries `readiness`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This campaign can't be scheduled yet. …","path":"/crm/marketing/campaign-manager/{id}/schedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Schedule a launch","description":"Books a `schedule` record owned by the campaign that launches it at `startDate` (and ends it at `endDate` when given), replacing any earlier booking, and sets the campaign `scheduled`. Only draft, scheduled or failed campaigns. An ad campaign is only booked when every platform it targets is ready (same check as launch).\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/schedule (id: string, body) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_SCHEDULABLE | A <status> campaign cannot be scheduled | Status is not draft, scheduled or failed. | — |\n| `409` | ads_not_ready | This campaign can't be scheduled yet. … | A targeted ad platform is not ready; body carries `readiness`. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"}}},"example":{"startDate":"2026-10-15T14:00:00Z","endDate":"2026-10-31T23:59:00Z"}}}}}},"/crm/marketing/campaign-manager/{id}/unschedule":{"post":{"operationId":"MarketingController_managerUnschedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This campaign is not scheduled — Status is not scheduled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This campaign is not scheduled","path":"/crm/marketing/campaign-manager/{id}/unschedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/unschedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Cancel a scheduled launch","description":"Deletes the pending schedule (which cancels its queued jobs) and returns the campaign to draft.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/unschedule (id: string) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_SCHEDULED | This campaign is not scheduled | Status is not scheduled. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/duplicate":{"post":{"operationId":"MarketingController_managerDuplicate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The new campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/duplicate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This campaign was just created — open it from Campaigns to keep editing it — The same duplicate was sent twice in a row.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This campaign was just created — open it from Campaigns to keep editing it","path":"/crm/marketing/campaign-manager/{id}/duplicate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Duplicate a campaign","description":"A new draft with the same settings and none of the runtime state (platform ids, results, metrics, schedule). `name` defaults to \"<name> (Copy)\".\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/duplicate (id: string, body) -> The new campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n| `409` | DUPLICATE_CREATE | This campaign was just created — open it from Campaigns to keep editing it | The same duplicate was sent twice in a row. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}}},"example":{"name":"Fall sale — TikTok"}}}}}},"/crm/marketing/campaign-manager/{id}/actuals":{"post":{"operationId":"MarketingController_managerActuals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"<field> must be a number of zero or more — A figure is negative or not a number.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"<field> must be a number of zero or more","path":"/crm/marketing/campaign-manager/{id}/actuals","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/actuals","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Enter actual results","description":"For channels no system reports on (radio, print, TV…): `spent`, `reach`, `impressions`, `clicks`, `conversions`, `revenue` (each ≥ 0) and an optional `note`. They replace the previous entry and count toward the card's metrics.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/actuals (id: string, body) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BAD_FIGURE | <field> must be a number of zero or more | A figure is negative or not a number. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"spent":{"type":"number"},"reach":{"type":"number"},"impressions":{"type":"number"},"clicks":{"type":"number"},"conversions":{"type":"number"},"revenue":{"type":"number"},"note":{"type":"string","maxLength":1000}}},"example":{"spent":1200,"reach":40000,"note":"Invoice from KXYZ radio"}}}}}},"/crm/marketing/campaign-manager/{id}/refresh-metrics":{"post":{"operationId":"MarketingController_managerRefresh","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This campaign is not live on any ad platform yet, so there are no platform insights to pull — No platform reference on the campaign.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This campaign is not live on any ad platform yet, so there are no platform insights to pull","path":"/crm/marketing/campaign-manager/{id}/refresh-metrics","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/refresh-metrics","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"No platform returned insights: <platform>: <error> — Every platform failed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"No platform returned insights: <platform>: <error>","path":"/crm/marketing/campaign-manager/{id}/refresh-metrics","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Pull platform insights","description":"Fetches spend, reach, impressions, clicks and conversions from every ad platform the campaign is live on and stores them (`platformMetrics`). Platforms that fail are listed in `platformMetrics.errors`.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/refresh-metrics (id: string) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_LIVE | This campaign is not live on any ad platform yet, so there are no platform insights to pull | No platform reference on the campaign. | — |\n| `502` | NO_INSIGHTS | No platform returned insights: <platform>: <error> | Every platform failed. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/copy":{"post":{"operationId":"MarketingController_managerCopy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Send at least one headline or description — Both lists empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Send at least one headline or description","path":"/crm/marketing/campaign-manager/{id}/copy","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/copy","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Save ad copy","description":"Saves headlines, descriptions and a call to action for one `platform` (default `all`) as the campaign's creative for that platform, replacing the previous one. Nothing is published.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/copy (id: string, body) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NO_COPY | Send at least one headline or description | Both lists empty. | — |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string"},"headlines":{"type":"array","items":{"type":"string"}},"descriptions":{"type":"array","items":{"type":"string"}},"cta":{"type":"string"}}},"example":{"platform":"facebook","headlines":["30% off this week"],"descriptions":["Ends Sunday."],"cta":"SHOP_NOW"}}}}}},"/crm/marketing/campaign-manager/{id}/ad-account":{"post":{"operationId":"MarketingController_managerSetAdAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card, with `adAccounts` and fresh `adsReadiness`.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/ad-account","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Override a campaign's ad account on one platform","description":"Launch this campaign from a different ad account than the connection default, on one platform (Facebook and Instagram share one Meta account). Verified exactly like the default. `accountId: null` returns to the default. Only while the campaign is a draft, scheduled or failed.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/ad-account (id: string, body) -> The campaign card, with `adAccounts` and fresh `adsReadiness`.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","example":"facebook"},"accountId":{"type":"string","nullable":true,"example":"act_36945266"}},"required":["platform"]}}}}}},"/crm/marketing/campaign-manager/{id}/test-launch":{"post":{"operationId":"MarketingController_managerTestLaunch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card with `testLaunch.launches`.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A paused test launch is available for Facebook and Instagram … — The campaign targets neither Facebook nor Instagram.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A paused test launch is available for Facebook and Instagram …","path":"/crm/marketing/campaign-manager/{id}/test-launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Test launch needs Meta to be ready first. … — Meta is not ready (no ad account chosen, missing permission…).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Test launch needs Meta to be ready first. …","path":"/crm/marketing/campaign-manager/{id}/test-launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Test-launch a campaign on Meta, paused","description":"Proves the Meta setup end to end without spending: creates the campaign on the chosen Meta ad account in **PAUSED** status, with no budget, no ad sets and no ads, reads it back, and deletes it at once if Meta reports anything other than PAUSED. The platform campaign id is kept on the campaign (`testLaunch.launches`) with an Ads Manager link. Only Meta supports this; other platforms are never test-created.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/test-launch (id: string) -> The campaign card with `testLaunch.launches`.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | ads_not_ready | Test launch needs Meta to be ready first. … | Meta is not ready (no ad account chosen, missing permission…). | Follow the `readiness` fixes. |\n| `400` | test_launch_unsupported | A paused test launch is available for Facebook and Instagram … | The campaign targets neither Facebook nor Instagram. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/campaign-manager/{id}/test-remove`","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager/{id}/test-remove":{"post":{"operationId":"MarketingController_managerRemoveTest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The campaign card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaign-manager/{id}/test-remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"The test could not be removed on Meta: … — Meta refused the delete.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"The test could not be removed on Meta: …","path":"/crm/marketing/campaign-manager/{id}/test-remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Remove a paused test campaign","description":"Deletes the paused test campaign on Meta and clears it from the campaign. Deleting the campaign record removes its test too.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaign-manager/{id}/test-remove (id: string) -> The campaign card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `502` | test_remove_failed | The test could not be removed on Meta: … | Meta refused the delete. | Retry, or delete it in Ads Manager. |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-manager-performance":{"get":{"operationId":"MarketingController_managerPerformance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ summary, topByConversions, failed, untracked, campaigns }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Campaign performance summary","description":"What the marketing AI answers \"how are my campaigns doing?\" from: the org totals, the top 5 campaigns by conversions, failed campaigns with their reason, untracked campaigns, and a compact row per campaign (first 100).\n\n#### Signature\n\n```http\nGET /crm/marketing/campaign-manager-performance () -> { summary, topByConversions, failed, untracked, campaigns }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["CRM · Marketing"]}},"/crm/marketing/social-profiles/{platform}":{"get":{"operationId":"MarketingController_getSocialProviders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":true,"in":"path","schema":{"type":"string"},"description":"One of `facebook`, `instagram`, `tiktok`, `twitter`, `linkedin`, `pinterest`. Omit for all.","example":"instagram"}],"responses":{"200":{"description":"Connected profiles","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get connected social profiles","description":"The social accounts available to publish marketing campaigns from. Supply `platform` to narrow it.\n\n#### Signature\n\n```http\nGET /crm/marketing/social-profiles/{platform} (platform: string) -> Connected profiles\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/platforms`","tags":["CRM · Marketing"]}},"/crm/marketing/campaigns":{"post":{"operationId":"MarketingController_createCampaign","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created campaign","content":{"application/json":{"schema":{"type":"object","description":"A marketing campaign.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a marketing campaign","description":"Creates a marketing campaign. Distinct from `/crm/ads` — that manages paid advertising on ad platforms; this covers owned marketing across channels.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaigns (body) -> The created campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaign-types`","tags":["CRM · Marketing"],"requestBody":{"description":"The campaign to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Summer newsletter"},"type":{"type":"string","example":"email"},"audienceId":{"type":"string","description":"Segment to target.","example":"AUD-4821"},"schedule":{"type":"object","additionalProperties":true}}},"example":{"name":"Summer newsletter","type":"email","audienceId":"AUD-4821"}}}}},"get":{"operationId":"MarketingController_getCampaigns","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by status."},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by type."},{"name":"platforms","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated platforms."},{"name":"tags","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated tags."},{"name":"createdBy","required":false,"in":"query","schema":{"type":"string"},"description":"Creator email."},{"name":"search","required":false,"in":"query","schema":{"type":"string"},"description":"Search text."},{"name":"budgetMin","required":false,"in":"query","schema":{"type":"integer"},"description":"Minimum budget."},{"name":"budgetMax","required":false,"in":"query","schema":{"type":"integer"},"description":"Maximum budget."},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — start of the range."},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — end of the range."},{"name":"sortDirection","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"page","in":"query","required":false,"description":"Page number (1-based).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows.","schema":{"type":"string"}},{"name":"sortField","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}}],"responses":{"200":{"description":"Marketing campaigns","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List marketing campaigns","description":"The org's marketing campaigns, with their status and schedule.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaigns (status?: string, type?: string, platforms?: string, tags?: string, createdBy?: string, search?: string, budgetMin?: integer, budgetMax?: integer, startDate?: string, endDate?: string, page?: string, limit?: string, sortField?: string, sortDirection?: string) -> Marketing campaigns\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaigns/{id}`","tags":["CRM · Marketing"]}},"/crm/marketing/campaigns/{id}":{"get":{"operationId":"MarketingController_getCampaign","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The campaign","content":{"application/json":{"schema":{"type":"object","description":"A marketing campaign.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaigns/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a marketing campaign","description":"Fetches one campaign with its targeting, schedule and current state.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaigns/{id} (id: string) -> The campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaigns/{id}/analytics`","tags":["CRM · Marketing"]},"put":{"operationId":"MarketingController_updateCampaign","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The updated campaign","content":{"application/json":{"schema":{"type":"object","description":"A marketing campaign.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaigns/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update a marketing campaign","description":"Updates a campaign. Changes to one already sent affect only future sends — a delivered email cannot be edited.\n\n#### Signature\n\n```http\nPUT /crm/marketing/campaigns/{id} (id: string, body) -> The updated campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/marketing/campaigns/{id}`","tags":["CRM · Marketing"],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Summer newsletter (v2)"}}}}},"delete":{"operationId":"MarketingController_deleteCampaign","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaigns/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a marketing campaign","description":"Deletes a campaign. Its historical performance data goes with it — export analytics first if you need them.\n\n#### Signature\n\n```http\nDELETE /crm/marketing/campaigns/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaigns/{id}/analytics`","tags":["CRM · Marketing"]}},"/crm/marketing/campaigns/{id}/analytics":{"get":{"operationId":"MarketingController_getCampaignAnalytics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — start of the range."},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — end of the range."}],"responses":{"200":{"description":"Campaign analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Campaign not found — No campaign has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Campaign not found","path":"/crm/marketing/campaigns/{id}/analytics","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get campaign analytics","description":"Performance for one campaign — sends, opens, clicks and conversions.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaigns/{id}/analytics (id: string, startDate?: string, endDate?: string) -> Campaign analytics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/campaigns/aggregated-metrics`","tags":["CRM · Marketing"]}},"/crm/marketing/campaigns/aggregated-metrics":{"post":{"operationId":"MarketingController_getAggregatedMetrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Aggregated metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get aggregated campaign metrics","description":"Combines metrics across several campaigns in one call — the read behind a marketing overview, rather than fetching analytics per campaign.\n\n#### Signature\n\n```http\nPOST /crm/marketing/campaigns/aggregated-metrics (body) -> Aggregated metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/dashboard`","tags":["CRM · Marketing"],"requestBody":{"description":"Which campaigns and period to aggregate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"campaignIds":{"type":"array","items":{"type":"string"}},"startDate":{"type":"string","example":"2026-08-01"},"endDate":{"type":"string","example":"2026-08-31"}}},"example":{"campaignIds":["CMP-4821","CMP-4822"],"startDate":"2026-08-01","endDate":"2026-08-31"}}}}}},"/crm/marketing/platforms":{"get":{"operationId":"MarketingController_getAvailablePlatforms","parameters":[{"name":"orgId","required":true,"in":"header","schema":{"type":"string"}},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Marketing platforms","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List marketing platforms","description":"The channels marketing campaigns can run on, and which are connected for this org.\n\n#### Signature\n\n```http\nGET /crm/marketing/platforms () -> Marketing platforms\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/campaign-types`","tags":["CRM · Marketing"]}},"/crm/marketing/campaign-types":{"get":{"operationId":"MarketingController_getCampaignTypes","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Campaign types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List campaign types","description":"The kinds of campaign that can be created, with the fields each requires. Read this to build a campaign form generically.\n\n#### Signature\n\n```http\nGET /crm/marketing/campaign-types () -> Campaign types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/campaigns`","tags":["CRM · Marketing"]}},"/crm/marketing/dashboard":{"get":{"operationId":"MarketingController_getCampaignDashboard","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — start of the range."},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — end of the range."}],"responses":{"200":{"description":"Dashboard figures","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get the marketing dashboard","description":"Headline marketing figures across campaigns and channels.\n\n#### Signature\n\n```http\nGET /crm/marketing/dashboard (startDate?: string, endDate?: string) -> Dashboard figures\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/health`","tags":["CRM · Marketing"]}},"/crm/marketing/health":{"get":{"operationId":"MarketingController_healthCheck","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Integration health","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get marketing health","description":"Whether the marketing integrations are connected and working — check this first when campaigns fail to send.\n\n#### Signature\n\n```http\nGET /crm/marketing/health () -> Integration health\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/platforms`","tags":["CRM · Marketing"]}},"/crm/marketing/audiences/segments":{"post":{"operationId":"AudienceController_createAudienceSegment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"createdBy","in":"query","required":false,"description":"Recorded as the creator. Defaults to the literal string `api`, **not** the calling user.","schema":{"type":"string","default":"api"},"example":"ada@example.com"}],"responses":{"201":{"description":"The created segment, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","description":"An audience segment.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"High-value repeat buyers"},"description":{"type":"string"},"criteria":{"type":"object","additionalProperties":true,"description":"The rules that define membership."},"estimatedReach":{"type":"number","description":"How many people match.","example":4200},"platforms":{"type":"array","items":{"type":"string"},"example":["facebook"]}}}}},"message":{"type":"string","example":"Audience segment created successfully"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create an audience segment","description":"Defines a reusable audience from targeting criteria, so campaigns can reference it instead of restating the rules.\n\nNote `createdBy` is a **query parameter** defaulting to the literal string `api` — it is not taken from the authenticated user, so pass it explicitly if you want a real name in the audit trail.\n\n#### Signature\n\n```http\nPOST /crm/marketing/audiences/segments (createdBy?: string, body) -> The created segment, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Responses on this controller are wrapped as `{ success, data, message }` rather than returning the record bare.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/audiences/estimate-reach`","tags":["CRM · Audiences"],"requestBody":{"description":"The segment to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"High-value repeat buyers"},"description":{"type":"string"},"criteria":{"type":"object","additionalProperties":true,"description":"The rules defining membership."}}},"example":{"name":"High-value repeat buyers","description":"Three or more orders over $200","criteria":{"minOrders":3,"minLifetimeValue":200}}}}}},"get":{"operationId":"AudienceController_getAudienceSegments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by status."},{"name":"platforms","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated platforms."},{"name":"sizeMin","required":false,"in":"query","schema":{"type":"integer"},"description":"Minimum audience size."},{"name":"sizeMax","required":false,"in":"query","schema":{"type":"integer"},"description":"Maximum audience size."},{"name":"ageMin","required":false,"in":"query","schema":{"type":"integer"},"description":"Minimum age."},{"name":"ageMax","required":false,"in":"query","schema":{"type":"integer"},"description":"Maximum age."},{"name":"locations","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated locations."},{"name":"interests","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated interests."},{"name":"behaviors","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated behaviors."},{"name":"createdBy","required":false,"in":"query","schema":{"type":"string"},"description":"Creator email."},{"name":"search","required":false,"in":"query","schema":{"type":"string"},"description":"Search text."},{"name":"sortDirection","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"page","in":"query","required":false,"description":"Page number (1-based).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows.","schema":{"type":"string"}},{"name":"sortField","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}}],"responses":{"200":{"description":"Audience segments, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"type":"object","description":"An audience segment.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"High-value repeat buyers"},"description":{"type":"string"},"criteria":{"type":"object","additionalProperties":true,"description":"The rules that define membership."},"estimatedReach":{"type":"number","description":"How many people match.","example":4200},"platforms":{"type":"array","items":{"type":"string"},"example":["facebook"]}}}}}},"message":{"type":"string","example":"Audience segments retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List audience segments","description":"The audience segments defined for the org.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/segments (status?: string, platforms?: string, sizeMin?: integer, sizeMax?: integer, ageMin?: integer, ageMax?: integer, locations?: string, interests?: string, behaviors?: string, createdBy?: string, search?: string, page?: string, limit?: string, sortField?: string, sortDirection?: string) -> Audience segments, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/segments/{id}`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/segments/{id}":{"get":{"operationId":"AudienceController_getAudienceSegment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Audience segment id.","example":"AUD-4821"}],"responses":{"200":{"description":"The segment, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","description":"An audience segment.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"High-value repeat buyers"},"description":{"type":"string"},"criteria":{"type":"object","additionalProperties":true,"description":"The rules that define membership."},"estimatedReach":{"type":"number","description":"How many people match.","example":4200},"platforms":{"type":"array","items":{"type":"string"},"example":["facebook"]}}}}},"message":{"type":"string","example":"Audience segment retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an audience segment","description":"Fetches one segment with its criteria and estimated reach.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/segments/{id} (id: string) -> The segment, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/marketing/audiences/segments/{id}`","tags":["CRM · Audiences"]},"put":{"operationId":"AudienceController_updateAudienceSegment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Audience segment id.","example":"AUD-4821"},{"name":"updatedBy","in":"query","required":false,"description":"Who made the change (recorded on the record).","schema":{"type":"string"}}],"responses":{"200":{"description":"The updated segment, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","description":"An audience segment.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"High-value repeat buyers"},"description":{"type":"string"},"criteria":{"type":"object","additionalProperties":true,"description":"The rules that define membership."},"estimatedReach":{"type":"number","description":"How many people match.","example":4200},"platforms":{"type":"array","items":{"type":"string"},"example":["facebook"]}}}}},"message":{"type":"string","example":"Audience segment updated"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update an audience segment","description":"Updates a segment's criteria or details. Changing criteria changes who is in the audience for **future** campaigns; campaigns already sent are unaffected.\n\n#### Signature\n\n```http\nPUT /crm/marketing/audiences/segments/{id} (id: string, updatedBy?: string, body) -> The updated segment, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/marketing/audiences/segments/{id}`","tags":["CRM · Audiences"],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"criteria":{"minOrders":5,"minLifetimeValue":500}}}}}},"delete":{"operationId":"AudienceController_deleteAudienceSegment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Audience segment id.","example":"AUD-4821"},{"name":"deletedBy","in":"query","required":false,"description":"Who deleted it (recorded).","schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","additionalProperties":true},"message":{"type":"string","example":"Audience segment deleted"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete an audience segment","description":"Deletes a segment. Campaigns still referencing it lose their targeting — check what uses it before deleting.\n\n#### Signature\n\n```http\nDELETE /crm/marketing/audiences/segments/{id} (id: string, deletedBy?: string) -> Deletion result, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Nothing checks for campaigns using the segment first.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/segments`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/custom":{"post":{"operationId":"AudienceController_createCustomAudience","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"createdBy","in":"query","required":false,"description":"Recorded as the creator. Defaults to the literal string `api`, **not** the calling user.","schema":{"type":"string","default":"api"},"example":"ada@example.com"}],"responses":{"201":{"description":"The created custom audience, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","additionalProperties":true},"message":{"type":"string","example":"Custom audience created successfully"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a custom audience","description":"Creates a platform-specific custom audience — an uploaded list or a lookalike — as opposed to a criteria-based segment.\n\n#### Signature\n\n```http\nPOST /crm/marketing/audiences/custom (createdBy?: string, body) -> The created custom audience, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Uploading customer data to an ad platform carries its own consent obligations — this endpoint does not check them.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/custom`","tags":["CRM · Audiences"],"requestBody":{"description":"The custom audience to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Newsletter subscribers"},"platform":{"type":"string","example":"facebook"},"type":{"type":"string","example":"customer_list"}}},"example":{"name":"Newsletter subscribers","platform":"facebook","type":"customer_list"}}}}},"get":{"operationId":"AudienceController_getCustomAudiences","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"example":"facebook"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"customer_list"}],"responses":{"200":{"description":"Custom audiences, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"message":{"type":"string","example":"Custom audiences retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List custom audiences","description":"The platform-specific custom audiences, optionally filtered by platform or type.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/custom (platform?: string, type?: string) -> Custom audiences, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/custom-types`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/segments/{id}/insights":{"get":{"operationId":"AudienceController_getAudienceInsights","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Audience segment id.","example":"AUD-4821"}],"responses":{"200":{"description":"Audience insights, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","additionalProperties":true},"message":{"type":"string","example":"Insights retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get audience insights","description":"Demographic and behavioural breakdown of who is in a segment — what the audience actually looks like, rather than just how large it is.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/segments/{id}/insights (id: string) -> Audience insights, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/segments/{id1}/overlap/{id2}`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/estimate-reach":{"post":{"operationId":"AudienceController_estimateAudienceReach","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The estimated reach, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"estimatedReach":{"type":"number","example":4200}}},"message":{"type":"string","example":"Reach estimated"}}},"example":{"success":true,"data":{"estimatedReach":4200}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Estimate audience reach","description":"Estimates how many people match a set of criteria **without creating a segment** — the way to size an audience while building it, before committing to it.\n\n#### Signature\n\n```http\nPOST /crm/marketing/audiences/estimate-reach (body) -> The estimated reach, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Creates nothing — safe to call repeatedly while tuning criteria.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/audiences/segments`","tags":["CRM · Audiences"],"requestBody":{"description":"The criteria to size.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"criteria":{"type":"object","additionalProperties":true}}},"example":{"criteria":{"minOrders":3,"minLifetimeValue":200}}}}}}},"/crm/marketing/audiences/segments/{id1}/overlap/{id2}":{"get":{"operationId":"AudienceController_getAudienceOverlap","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id1","required":true,"in":"path","schema":{"type":"string"},"description":"First segment.","example":"AUD-4821"},{"name":"id2","required":true,"in":"path","schema":{"type":"string"},"description":"Second segment.","example":"AUD-4822"}],"responses":{"200":{"description":"The overlap, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","additionalProperties":true},"message":{"type":"string","example":"Overlap calculated"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get overlap between two audiences","description":"How much two segments share. Worth checking before running campaigns against both — a large overlap means the same people receive both, which reads as spam and skews per-campaign attribution.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/segments/{id1}/overlap/{id2} (id1: string, id2: string) -> The overlap, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/segments/{id}/insights`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/interests":{"get":{"operationId":"AudienceController_getPopularInterests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"category","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Interests, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"message":{"type":"string","example":"Interests retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List targetable interests","description":"The interest categories available for targeting — the vocabulary a segment's criteria can draw on.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/interests (category?: string) -> Interests, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/targeting-options`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/locations":{"get":{"operationId":"AudienceController_getLocationSuggestions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"query","required":false,"in":"query","schema":{"type":"string"},"description":"Location search text."}],"responses":{"200":{"description":"Locations, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"message":{"type":"string","example":"Locations retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List targetable locations","description":"The geographic locations available for targeting.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/locations (query?: string) -> Locations, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/targeting-options`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/custom-types":{"get":{"operationId":"AudienceController_getCustomAudienceTypes","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Custom audience types, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"message":{"type":"string","example":"Types retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List custom audience types","description":"The kinds of custom audience that can be created — customer list, lookalike, engagement-based and so on.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/custom-types () -> Custom audience types, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/audiences/custom`","tags":["CRM · Audiences"]}},"/crm/marketing/audiences/targeting-options":{"get":{"operationId":"AudienceController_getTargetingOptions","parameters":[{"name":"orgId","required":false,"in":"query","schema":{"type":"string"},"description":"Organization id (the `orgid` header is what scopes the request)."},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Targeting options, wrapped","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","additionalProperties":true},"message":{"type":"string","example":"Targeting options retrieved"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List all targeting options","description":"Every dimension a segment can target on, in one call — build a targeting UI from this rather than fetching interests and locations separately.\n\n#### Signature\n\n```http\nGET /crm/marketing/audiences/targeting-options (orgId?: string) -> Targeting options, wrapped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/marketing/audiences/interests`\n- `GET /crm/marketing/audiences/locations`","tags":["CRM · Audiences"]}},"/crm/leads/sla/sweep":{"post":{"operationId":"LeadsController_runSlaSweep","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"{ ran: true, lastRun }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"],"summary":"Run the stage-SLA sweep now","description":"Root admins only. Runs the hourly job that chases leads sitting in a stage past its SLA, instead of waiting for the next run.\n\n#### Signature\n\n```http\nPOST /crm/leads/sla/sweep () -> { ran: true, lastRun }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootSystem`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/crm/leads/import":{"post":{"operationId":"LeadsController_importLeads","summary":"Import leads from spreadsheet rows","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ total, created, skipped, failed, errors: [{ row, error }], skippedRows: [{ row, reason }] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"No rows to import — `rows` missing or empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No rows to import","path":"/crm/leads/import","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"],"description":"Creates a lead per row (at most 5,000 per call). Column names from a HubSpot export are recognised. A row with no usable data is skipped, and so is a row whose email is already a lead (or appears earlier in the same import). `pipelineId` / `stageId` place every new lead. Each row is created independently: one failing row is reported and the rest carry on.\n\n#### Signature\n\n```http\nPOST /crm/leads/import (body) -> { total, created, skipped, failed, errors: [{ row, error }], skippedRows: [{ row, reason }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NO_ROWS | No rows to import | `rows` missing or empty. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","items":{"type":"object","additionalProperties":true}},"pipelineId":{"type":"string"},"stageId":{"type":"string"}}},"example":{"rows":[{"First Name":"Ada","Last Name":"Lovelace","Email":"ada@example.com","Company":"Analytical Ltd"}],"pipelineId":"PL-1"}}}}}},"/crm/leads/detail":{"post":{"operationId":"LeadsController_createLead","summary":"Create a lead","description":"Creates a lead. **Validation is deliberately minimal** — almost every field is optional, because the point is to capture whatever a form or a phone call produced and fill in the rest later.\n\nOnly two rules apply: an `email`, *if supplied*, must be well-formed; and `status` defaults to `new` when omitted. A lead with nothing but a first name is valid.\n\nA duplicate on a unique field is reported as a `409` naming the field.\n\nLead errors carry a richer body than the platform default — `{ statusCode, message, error?, suggestion? }`. The `suggestion` is written for a person and is safe to show in a UI.\n\n#### Signature\n\n```http\nPOST /crm/leads/detail (body) -> The created lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Empty is better than wrong here — omit a field you are unsure of rather than guessing, and enrich it later.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_EMAIL | Invalid email format: \"<email>\" | An `email` was supplied and is not well-formed. | Fix the address, or leave it blank and add it later — a lead does not need one. |\n| `409` | DUPLICATE_LEAD | A lead with this <field> already exists | A unique field collides with an existing lead. | Update the existing lead instead, or use different contact details. The message names the offending field. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/enrich/{id}`\n- `GET /crm/leads/detail`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The lead to create. Nearly everything is optional.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}},"examples":{"minimal":{"summary":"Almost nothing","description":"Entirely valid — enrich it later.","value":{"firstName":"Ada"}},"typical":{"summary":"A form submission","value":{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com","phone":"+15551234567","company":"Analytical Engines Ltd","source":"website"}},"intoPipeline":{"summary":"Straight into a pipeline stage","value":{"firstName":"Ada","email":"ada@example.com","pipelineId":"PIPE-4821","stageId":"STG-1","priority":"high"}}}}}},"responses":{"201":{"description":"The created lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Invalid email format: \"<email>\" — An `email` was supplied and is not well-formed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"message":"Invalid email format: \"ada@\"","suggestion":"Fix the email or leave it blank to add later"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"A lead with this <field> already exists — A unique field collides with an existing lead.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"message":"A lead with this email already exists","error":"DUPLICATE_LEAD","suggestion":"Try updating the existing lead or use different contact information"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]},"get":{"operationId":"LeadsController_getLeads","summary":"List leads","description":"Lists leads with filtering, sorting and paging — the pipeline list view.\n\n#### Signature\n\n```http\nGET /crm/leads/detail (pipeline?: string, status?: string, priority?: string, source?: string, assignedTo?: string, search?: string, page?: integer, pageSize?: integer, sortField?: string, sortDirection?: string) -> A page of leads\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | OPERATION_FAILED | Failed to retrieve leads | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /crm/leads/detail/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"]},"example":"new"},{"name":"priority","required":false,"in":"query","schema":{"type":"string"},"example":"high"},{"name":"source","required":false,"in":"query","schema":{"type":"string"},"example":"facebook"},{"name":"assignedTo","required":false,"in":"query","description":"Filter to one owner.","schema":{"type":"string"},"example":"sales@acme.com"},{"name":"search","required":false,"in":"query","description":"Free-text search across name, email and company.","schema":{"type":"string"},"example":"lovelace"},{"name":"pipeline","required":false,"in":"query","schema":{"type":"string"},"description":"Pipeline id."},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"sortField","required":false,"in":"query","description":"Field to sort by.","schema":{"type":"string"},"example":"createdAt"},{"name":"sortDirection","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]},"example":"desc"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"responses":{"200":{"description":"A page of leads","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}},"total":{"type":"integer","example":412}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to retrieve leads — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to retrieve leads","path":"/crm/leads/detail","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Leads"]}},"/crm/leads/detail/{id}":{"get":{"operationId":"LeadsController_getLead","summary":"Get a lead","description":"Fetches one lead with its full record, including score and pipeline position.\n\n#### Signature\n\n```http\nGET /crm/leads/detail/{id} (id: string) -> The lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/leads/detail/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"responses":{"200":{"description":"The lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/detail/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]},"put":{"operationId":"LeadsController_updateLead","summary":"Update a lead","description":"Updates a lead's fields. Use the dedicated lifecycle endpoints for qualification, conversion and assignment — those record the transition, this one just writes fields.\n\n#### Signature\n\n```http\nPUT /crm/leads/detail/{id} (id: string, body) -> The updated lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Setting `status` directly here bypasses the qualify/disqualify/convert endpoints and records no transition.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n| `500` | OPERATION_FAILED | Failed to update lead | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /crm/leads/qualify/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}},"example":{"phone":"+15551234567","company":"Analytical Engines Ltd","priority":"high"}}}},"responses":{"200":{"description":"The updated lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/detail/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to update lead — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to update lead","path":"/crm/leads/detail/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Leads"]},"delete":{"operationId":"LeadsController_deleteLead","summary":"Delete a lead","description":"Permanently deletes a lead. Disqualify it instead when you want to keep the record of why it went nowhere.\n\n#### Signature\n\n```http\nDELETE /crm/leads/detail/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | OPERATION_FAILED | Failed to delete lead | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /crm/leads/disqualify/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to delete lead — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to delete lead","path":"/crm/leads/detail/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Leads"]}},"/crm/leads/qualify/{id}":{"post":{"operationId":"LeadsController_qualifyLead","summary":"Qualify a lead","description":"Marks a lead as qualified — worth pursuing. Notes are recorded with the transition.\n\n#### Signature\n\n```http\nPOST /crm/leads/qualify/{id} (id: string, body) -> The qualified lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/disqualify/{id}`\n- `POST /crm/leads/convert/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"requestBody":{"description":"Optional qualification notes.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","description":"Recorded with the transition.","example":"Budget confirmed, decision maker identified"}}},"example":{"notes":"Budget confirmed, decision maker identified"}}}},"responses":{"201":{"description":"The qualified lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/qualify/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/disqualify/{id}":{"post":{"operationId":"LeadsController_disqualifyLead","summary":"Disqualify a lead","description":"Marks a lead as not worth pursuing, recording why. The record is kept, which is what makes source quality measurable later.\n\n#### Signature\n\n```http\nPOST /crm/leads/disqualify/{id} (id: string, body) -> The disqualified lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Prefer this to deleting — a disqualified lead with a reason is data; a deleted one is nothing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/qualify/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"requestBody":{"description":"Why the lead was disqualified.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Recorded with the transition.","example":"No budget this financial year"}}},"example":{"reason":"No budget this financial year"}}}},"responses":{"201":{"description":"The disqualified lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/disqualify/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/convert/{id}":{"post":{"operationId":"LeadsController_convertLead","summary":"Convert a lead","description":"Converts a lead into a customer. Pass `customerId` to link it to an existing customer, or omit it to have one created.\n\n`conversionValue` records what the conversion was worth, which is what makes the analytics endpoint able to report value by source rather than just counts.\n\n#### Signature\n\n```http\nPOST /crm/leads/convert/{id} (id: string, body) -> The converted lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Always send `conversionValue` where you know it — without it, conversion analytics only counts leads, not revenue.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/leads/analytics`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"requestBody":{"description":"Conversion details.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Existing customer to link to. Omit to create one.","example":"cus_4821"},"conversionValue":{"type":"number","description":"Value of the conversion, for reporting.","example":4500}}},"examples":{"newCustomer":{"summary":"Convert and create a customer","value":{"conversionValue":4500}},"existing":{"summary":"Link to an existing customer","value":{"customerId":"cus_4821","conversionValue":4500}}}}}},"responses":{"201":{"description":"The converted lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/convert/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/assign/{id}":{"post":{"operationId":"LeadsController_assignLead","summary":"Assign a lead","description":"Assigns a lead to a user, making it appear in their queue. Assigning an already-assigned lead reassigns it.\n\n#### Signature\n\n```http\nPOST /crm/leads/assign/{id} (id: string, body) -> The assigned lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/pipelines/{id}/route-unassigned`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"requestBody":{"description":"Who to assign it to.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignedTo"],"properties":{"assignedTo":{"type":"string","description":"User to own the lead.","example":"sales@acme.com"}}},"example":{"assignedTo":"sales@acme.com"}}}},"responses":{"201":{"description":"The assigned lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/assign/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/follow-up/{id}":{"post":{"operationId":"LeadsController_scheduleFollowUp","summary":"Schedule a follow-up","description":"Sets a follow-up date on a lead so it resurfaces at the right time. Recording *why* in `notes` is what makes the reminder useful when it arrives.\n\n#### Signature\n\n```http\nPOST /crm/leads/follow-up/{id} (id: string, body) -> The updated lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/activities/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"requestBody":{"description":"When to follow up, and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["followUpDate"],"properties":{"followUpDate":{"type":"string","format":"date-time","example":"2026-09-15T09:00:00.000Z"},"notes":{"type":"string","example":"Check whether the new budget was approved"}}},"example":{"followUpDate":"2026-09-15T09:00:00.000Z","notes":"Check whether the new budget was approved"}}}},"responses":{"201":{"description":"The updated lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/follow-up/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/forecast":{"get":{"operationId":"LeadsController_getForecast","summary":"Pipeline forecast","description":"A weighted forecast by expected close month and by owner. Open leads count at their stage's probability; a deal is a lead at a won stage. Months run from this month for `months` (1–24, default 6); earlier dates fall into `overdue`, later ones into `later`, undated into `unscheduled`. Totals flag leads with no value (`unpriced`) and stages with no probability.\n\n#### Signature\n\n```http\nGET /crm/leads/forecast (pipelineId?: string, months?: integer) -> { months, byMonth, byOwner, totals: { openCount, openValue, weighted, wonCount, wonValue, lostCount, unpriced, withoutProbability }, pipelines }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pipelineId","required":false,"in":"query","schema":{"type":"string"}},{"name":"months","required":false,"in":"query","schema":{"type":"integer","default":6}},{"name":"endDate","required":false,"in":"query","description":"End date for analytics","schema":{}},{"name":"startDate","required":false,"in":"query","description":"Start date for analytics","schema":{}},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"responses":{"200":{"description":"{ months, byMonth, byOwner, totals: { openCount, openValue, weighted, wonCount, wonValue, lostCount, unpriced, withoutProbability }, pipelines }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/analytics":{"get":{"operationId":"LeadsController_getLeadAnalytics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — start of the range."},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date — end of the range."}],"responses":{"200":{"description":"Aggregate lead analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to retrieve lead analytics — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to retrieve lead analytics","path":"/crm/leads/analytics","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Leads"],"summary":"Get lead analytics","description":"Aggregate lead figures — volume by status and source, conversion rates and value. Conversion value is only meaningful where `conversionValue` was recorded at conversion time.\n\n#### Signature\n\n```http\nGET /crm/leads/analytics (startDate?: string, endDate?: string) -> Aggregate lead analytics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | OPERATION_FAILED | Failed to retrieve lead analytics | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /crm/leads/convert/{id}`"}},"/crm/leads/enrich/batch":{"post":{"operationId":"LeadsController_enrichLeadsBatch","summary":"Enrich several leads","description":"Enriches many leads in one call. Enrichment usually costs money per lead at the provider, so send only the leads that need it.\n\n#### Signature\n\n```http\nPOST /crm/leads/enrich/batch (body) -> Per-lead enrichment results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Usually billed per lead by the enrichment provider.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/enrich/auto`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"requestBody":{"description":"Which leads to enrich.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["leadIds"],"properties":{"leadIds":{"type":"array","items":{"type":"string"},"description":"Lead ids to enrich.","example":["LEAD-4821","LEAD-4822"]}}},"example":{"leadIds":["LEAD-4821","LEAD-4822"]}}}},"responses":{"201":{"description":"Per-lead enrichment results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/enrichment/status/{id}":{"get":{"operationId":"LeadsController_getLeadEnrichmentStatus","summary":"Get enrichment status","description":"Reports where a lead's enrichment has got to. Poll this after starting enrichment rather than assuming it completed synchronously.\n\n#### Signature\n\n```http\nGET /crm/leads/enrichment/status/{id} (id: string) -> The enrichment status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/enrich/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"responses":{"200":{"description":"The enrichment status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/enrichment/status/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/enrich/auto":{"post":{"operationId":"LeadsController_autoEnrichLeads","summary":"Enrich leads automatically","description":"Runs enrichment across leads that qualify for it automatically, rather than naming them individually. Like the batch form, this can incur per-lead provider costs.\n\n#### Signature\n\n```http\nPOST /crm/leads/enrich/auto (limit?: string, body) -> The enrichment result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Selects leads on its own — check what it would touch before running it on a large org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/enrich/batch`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","description":"Maximum rows.","schema":{"type":"string"}},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"responses":{"201":{"description":"The enrichment result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"],"requestBody":{"description":"Optional enrichment settings.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/crm/leads/enrich/{id}":{"post":{"operationId":"LeadsController_enrichLead","summary":"Enrich a lead","description":"Fills in missing detail on a lead from external data sources — the counterpart to the deliberately thin creation rules. Capture what you have, then enrich.\n\n#### Signature\n\n```http\nPOST /crm/leads/enrich/{id} (id: string) -> The enrichment result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Enrichment may run asynchronously — poll `GET /crm/leads/enrichment/status/{id}` rather than assuming the response is final.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/leads/enrichment/status/{id}`\n- `POST /crm/leads/enrich/batch`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"},{"name":"orgId","required":true,"in":"query","description":"Organization ID","schema":{}}],"responses":{"201":{"description":"The enrichment result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/enrich/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/pipelines":{"post":{"operationId":"LeadsController_createPipeline","summary":"Create a lead pipeline","description":"Creates a pipeline — the ordered stages a lead moves through. Stages can be added, reordered and removed afterwards.\n\n#### Signature\n\n```http\nPOST /crm/leads/pipelines (body) -> The created pipeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | OPERATION_FAILED | Failed to create pipeline | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /crm/leads/pipelines/stages/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The pipeline to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Initial stages, in order."}}},"example":{"name":"Inbound sales","stages":[{"name":"New"},{"name":"Contacted"},{"name":"Demo booked"}]}}}},"responses":{"201":{"description":"The created pipeline","content":{"application/json":{"schema":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to create pipeline — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to create pipeline","path":"/crm/leads/pipelines","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Pipelines"]},"get":{"operationId":"LeadsController_getPipelines","summary":"List lead pipelines","description":"All lead pipelines in the org, with their stages.\n\n#### Signature\n\n```http\nGET /crm/leads/pipelines () -> The org's pipelines\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | OPERATION_FAILED | Failed to retrieve pipelines | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /crm/leads/pipelines/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The org's pipelines","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to retrieve pipelines — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to retrieve pipelines","path":"/crm/leads/pipelines","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Pipelines"]}},"/crm/leads/pipelines/{id}":{"get":{"operationId":"LeadsController_getPipeline","summary":"Get a lead pipeline","description":"Fetches one pipeline with its ordered stages.\n\n#### Signature\n\n```http\nGET /crm/leads/pipelines/{id} (id: string) -> The pipeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n| `500` | OPERATION_FAILED | Failed to retrieve pipeline | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /crm/leads/pipelines/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"}],"responses":{"200":{"description":"The pipeline","content":{"application/json":{"schema":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to retrieve pipeline — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to retrieve pipeline","path":"/crm/leads/pipelines/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Pipelines"]},"put":{"operationId":"LeadsController_updatePipeline","summary":"Update a lead pipeline","description":"Updates a pipeline's own fields. Use the stage endpoints to change its stages.\n\n#### Signature\n\n```http\nPUT /crm/leads/pipelines/{id} (id: string, body) -> The updated pipeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n| `500` | OPERATION_FAILED | Failed to update pipeline | An unexpected failure in the leads service. | Retry with backoff. The message is fixed text and does not indicate what went wrong. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `PUT /crm/leads/pipelines/stages/{id}/{stageId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Inbound sales (EMEA)"}}}},"responses":{"200":{"description":"The updated pipeline","content":{"application/json":{"schema":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to update pipeline — An unexpected failure in the leads service.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to update pipeline","path":"/crm/leads/pipelines/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["CRM · Pipelines"]},"delete":{"operationId":"LeadsController_deletePipeline","summary":"Delete a lead pipeline","description":"Deletes a pipeline. **A pipeline still holding leads cannot be deleted** — move or delete those leads first, so none are left pointing at a stage that no longer exists.\n\n#### Signature\n\n```http\nDELETE /crm/leads/pipelines/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n| `400` | PIPELINE_IN_USE | Cannot delete pipeline with existing leads | Leads are still assigned to this pipeline. | Move the leads to another pipeline first, or delete them. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/leads/detail`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot delete pipeline with existing leads — Leads are still assigned to this pipeline.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot delete pipeline with existing leads","path":"/crm/leads/pipelines/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Pipelines"]}},"/crm/leads/pipelines/{id}/route-unassigned":{"post":{"operationId":"LeadsController_routeUnassignedInPipeline","summary":"Route unassigned leads in a pipeline","description":"Distributes the pipeline's unassigned leads across owners according to its routing rules — the bulk alternative to assigning them one at a time.\n\n#### Signature\n\n```http\nPOST /crm/leads/pipelines/{id}/route-unassigned (id: string) -> The routing result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/assign/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"}],"responses":{"201":{"description":"The routing result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/{id}/route-unassigned","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Pipelines"]}},"/crm/leads/pipelines/stages/{id}":{"post":{"operationId":"LeadsController_addPipelineStage","summary":"Add a stage to a pipeline","description":"Appends a stage to a pipeline. Use the reorder endpoint to place it somewhere other than the end.\n\n#### Signature\n\n```http\nPOST /crm/leads/pipelines/stages/{id} (id: string, body) -> The updated pipeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/leads/pipelines/stages/reorder/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"}],"requestBody":{"description":"The stage to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Proposal sent"}}},"example":{"name":"Proposal sent"}}}},"responses":{"201":{"description":"The updated pipeline","content":{"application/json":{"schema":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/stages/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Pipelines"]}},"/crm/leads/pipelines/stages/{id}/{stageId}":{"put":{"operationId":"LeadsController_updatePipelineStage","summary":"Update a pipeline stage","description":"Updates one stage within a pipeline.\n\n#### Signature\n\n```http\nPUT /crm/leads/pipelines/stages/{id}/{stageId} (id: string, stageId: string, body) -> The updated pipeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/leads/pipelines/stages/{id}/{stageId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"},{"name":"stageId","required":true,"in":"path","description":"Stage id within the pipeline.","schema":{"type":"string"},"example":"STG-2"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Proposal sent (v2)"}}}},"responses":{"200":{"description":"The updated pipeline","content":{"application/json":{"schema":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/stages/{id}/{stageId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Pipelines"]},"delete":{"operationId":"LeadsController_deletePipelineStage","summary":"Delete a pipeline stage","description":"Removes a stage from a pipeline. Move any leads sitting in it first — nothing here relocates them, and a lead pointing at a deleted stage falls out of the board.\n\n#### Signature\n\n```http\nDELETE /crm/leads/pipelines/stages/{id}/{stageId} (id: string, stageId: string) -> The updated pipeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Leads in the deleted stage are not moved — check the stage is empty first.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/leads/pipelines/stages/reorder/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"},{"name":"stageId","required":true,"in":"path","description":"Stage id within the pipeline.","schema":{"type":"string"},"example":"STG-2"}],"responses":{"200":{"description":"The updated pipeline","content":{"application/json":{"schema":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/stages/{id}/{stageId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Pipelines"]}},"/crm/leads/pipelines/stages/reorder/{id}":{"put":{"operationId":"LeadsController_reorderPipelineStages","summary":"Reorder pipeline stages","description":"Changes the order of a pipeline's stages. Send the stage ids in the order you want them.\n\n#### Signature\n\n```http\nPUT /crm/leads/pipelines/stages/reorder/{id} (id: string, body) -> The reordered pipeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Send every stage — an omitted one may lose its position.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIPELINE_NOT_FOUND | Pipeline not found | No pipeline in the org has that id. | List pipelines with `GET /crm/leads/pipelines`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/pipelines/stages/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Pipeline id.","schema":{"type":"string"},"example":"PIPE-4821"}],"requestBody":{"description":"The stages in their new order.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"stageIds":{"type":"array","items":{"type":"string"},"description":"Stage ids in the intended order.","example":["STG-1","STG-3","STG-2"]}}},"example":{"stageIds":["STG-1","STG-3","STG-2"]}}}},"responses":{"200":{"description":"The reordered pipeline","content":{"application/json":{"schema":{"type":"object","description":"A lead pipeline.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"Inbound sales"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Pipeline not found — No pipeline in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Pipeline not found","path":"/crm/leads/pipelines/stages/reorder/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Pipelines"]}},"/crm/leads/activities/{id}":{"post":{"operationId":"LeadsController_addActivity","summary":"Add an activity to a lead","description":"Records something that happened with a lead — a call, an email, a meeting. This is the interaction history a salesperson reads before picking up the phone.\n\n#### Signature\n\n```http\nPOST /crm/leads/activities/{id} (id: string, body) -> The recorded activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/leads/activities/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"}],"requestBody":{"description":"The activity to record.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"type":{"type":"string","description":"What kind of interaction.","example":"call"},"notes":{"type":"string","example":"Discussed pricing, sending a proposal"},"date":{"type":"string","format":"date-time"}}},"example":{"type":"call","notes":"Discussed pricing, sending a proposal","date":"2026-08-29T14:00:00.000Z"}}}},"responses":{"201":{"description":"The recorded activity","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/activities/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]},"get":{"operationId":"LeadsController_getActivities","summary":"Get a lead's activities","description":"The full interaction history for a lead.\n\n#### Signature\n\n```http\nGET /crm/leads/activities/{id} (id: string) -> The lead's activities\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/activities/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"}],"responses":{"200":{"description":"The lead's activities","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/activities/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/score/{id}":{"post":{"operationId":"LeadsController_calculateScore","summary":"Calculate a lead score","description":"Scores one lead. Pass a rules array to score against specific criteria, or omit it to use the org's configured rules.\n\n#### Signature\n\n```http\nPOST /crm/leads/score/{id} (id: string, body) -> The lead with its computed score\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The body is a bare array, not an object wrapping one.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEAD_NOT_FOUND | Lead not found | No lead in the org has that id. | Check the id with `GET /crm/leads/detail`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/score/batch`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Lead id.","schema":{"type":"string"},"example":"LEAD-4821"}],"requestBody":{"description":"Scoring rules. Omit to use the configured ones.","required":false,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Rules to score against."},"example":[{"field":"company","condition":"exists","points":10}]}}},"responses":{"201":{"description":"The lead with its computed score","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Lead not found — No lead in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Lead not found","path":"/crm/leads/score/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/score/batch":{"post":{"operationId":"LeadsController_batchCalculateScores","summary":"Score all leads","description":"Recomputes scores across **every** lead in the org. Run it after changing scoring rules; it is a heavy operation, not something to call per page view.\n\n#### Signature\n\n```http\nPOST /crm/leads/score/batch (body) -> The batch scoring result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Touches every lead in the org — treat it as an administrative operation.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/score/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Scoring rules. Omit to use the configured ones.","required":false,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}},"example":[{"field":"company","condition":"exists","points":10}]}}},"responses":{"201":{"description":"The batch scoring result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/import/facebook":{"post":{"operationId":"LeadsController_importFromFacebook","summary":"Import leads from Facebook","description":"Queues a sync of Facebook lead-ad submissions into the CRM.\n\n**The work is queued, not performed.** The response confirms the job was accepted — `{ success: true, message: \"Facebook lead sync queued\" }` — and says nothing about how many leads arrived. Check the lead list afterwards.\n\n#### Signature\n\n```http\nPOST /crm/leads/import/facebook (body) -> Confirmation that the sync was queued\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Asynchronous — a `200` means queued, not imported.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/import/contacts`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Optional import settings.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}},"responses":{"200":{"description":"Facebook leads imported successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"leadsImported":{"type":"number"},"details":{"type":"array"}}}}}},"201":{"description":"Confirmation that the sync was queued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Facebook lead sync queued"}}},"example":{"success":true,"message":"Facebook lead sync queued"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/import/contacts":{"post":{"operationId":"LeadsController_importFromContacts","summary":"Import leads from contacts","description":"Turns existing CRM contacts into leads, optionally dropping them straight into a pipeline stage.\n\nSet `skipDuplicates` to leave contacts that are already leads alone — without it, importing the same contacts twice can produce duplicates.\n\n#### Signature\n\n```http\nPOST /crm/leads/import/contacts (body) -> The import result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Omitting `contactIds` imports every contact — check the scope before running it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/bulk-import/contacts`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"Which contacts to import, and where to put them.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contactIds":{"type":"array","items":{"type":"string"},"description":"Contacts to import. Omit to import all.","example":["cus_4821"]},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-1"},"source":{"type":"string","description":"Recorded as the lead source.","example":"contact-import"},"skipDuplicates":{"type":"boolean","default":false,"description":"Skip contacts that are already leads.","example":true}}},"example":{"contactIds":["cus_4821","cus_4822"],"pipelineId":"PIPE-4821","stageId":"STG-1","source":"contact-import","skipDuplicates":true}}}},"responses":{"201":{"description":"The import result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/convert/contact/{contactId}":{"post":{"operationId":"LeadsController_convertContactToLead","summary":"Convert one contact into a lead","description":"Creates a lead from a single existing contact — the one-at-a-time counterpart to the import endpoints, for when someone in the contact book turns into an opportunity.\n\n#### Signature\n\n```http\nPOST /crm/leads/convert/contact/{contactId} (contactId: string, body) -> The created lead\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/import/contacts`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"contactId","required":true,"in":"path","description":"Contact id.","schema":{"type":"string"},"example":"cus_4821"}],"requestBody":{"description":"Where to place the new lead.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-1"},"source":{"type":"string","example":"referral"}}},"example":{"pipelineId":"PIPE-4821","stageId":"STG-1","source":"referral"}}}},"responses":{"201":{"description":"The created lead","content":{"application/json":{"schema":{"type":"object","description":"A lead (`crm_lead`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","description":"Validated **only when supplied** — a lead may have none.","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"},"company":{"type":"string","example":"Analytical Engines Ltd"},"status":{"type":"string","enum":["new","contacted","qualified","disqualified","converted"],"description":"Defaults to `new` when not supplied.","example":"new"},"priority":{"type":"string","description":"Lead priority.","example":"high"},"source":{"type":"string","description":"Where the lead came from.","example":"facebook"},"assignedTo":{"type":"string","description":"User the lead is assigned to.","example":"sales@acme.com"},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-2"},"score":{"type":"number","description":"Computed by the scoring endpoints.","example":72},"conversionValue":{"type":"number","description":"Recorded at conversion.","example":4500},"followUpDate":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/leads/bulk-import/contacts":{"post":{"operationId":"LeadsController_bulkImportContacts","summary":"Bulk import contacts as leads","description":"The bulk form of the contact import, for larger sets. `contactIds` is required here rather than optional, so it cannot accidentally import the whole contact book.\n\n#### Signature\n\n```http\nPOST /crm/leads/bulk-import/contacts (body) -> The import result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/leads/import/contacts`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The contacts to import.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["contactIds"],"properties":{"contactIds":{"type":"array","items":{"type":"string"},"description":"Contacts to import. Required.","example":["cus_4821","cus_4822"]},"pipelineId":{"type":"string","example":"PIPE-4821"},"stageId":{"type":"string","example":"STG-1"},"source":{"type":"string","example":"contact-import"}}},"example":{"contactIds":["cus_4821","cus_4822"],"pipelineId":"PIPE-4821","stageId":"STG-1"}}}},"responses":{"201":{"description":"The import result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Leads"]}},"/crm/events/get/{id}":{"get":{"operationId":"EventsController_getEvents","summary":"Get CRM events","description":"Fetches one CRM event by id, or lists them when the segment is omitted.\n\n#### Signature\n\n```http\nGET /crm/events/get/{id} (id: string) -> Events\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/events/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Event id. Omit to list.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Events","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Events"]}},"/crm/events/delete/{id}":{"delete":{"operationId":"EventsController_deleteEvent","summary":"Delete a CRM event","description":"Deletes a CRM event.\n\n#### Signature\n\n```http\nDELETE /crm/events/delete/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/events/get/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Record id.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Events"]}},"/crm/events/create":{"post":{"operationId":"EventsController_createEvent","summary":"Create a CRM event","description":"Creates a CRM event record.\n\n#### Signature\n\n```http\nPOST /crm/events/create (body) -> The created event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/events/update`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The event to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"data":{"title":"Quarterly customer webinar","startDate":"2026-09-15T14:00:00.000Z"}}}}},"responses":{"201":{"description":"The created event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Events"]}},"/crm/events/update":{"post":{"operationId":"EventsController_updateEvent","summary":"Update a CRM event","description":"Updates an existing CRM event. Include its `sk`.\n\n#### Signature\n\n```http\nPOST /crm/events/update (body) -> The updated event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/events/delete/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The event to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f1a2b3c4d5e6f708192a3b","data":{"title":"Quarterly customer webinar (rescheduled)"}}}}},"responses":{"201":{"description":"The updated event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Events"]}},"/crm/reservations/definitions":{"get":{"operationId":"ReservationsController_getReservationDefinitions","summary":"Get reservation definitions","description":"The reservation types the org offers, with their schemas — read this to build a booking form generically rather than hard-coding appointment types.\n\n#### Signature\n\n```http\nGET /crm/reservations/definitions () -> Reservation definitions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/slots`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Reservation definitions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/definitions","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/slots":{"post":{"operationId":"ReservationsController_generateAppointmentSlots","summary":"Generate appointment slots","description":"Computes the bookable slots for a period from the service point's availability and existing reservations.\n\nSlots are calculated, not reserved — one returned here can be taken by someone else before you book it.\n\nThe definition record must be datatype **`reservation_definition`**. Creating it as `crm_reservation_definition` succeeds and then every slot request answers \"Reservation Definition not found\", because the generator looks in the other collection.\n\nSlot times come back in UTC with `businessTimezone` alongside. Format for display in the business timezone, not the visitor's — a customer abroad shown their own local time books an appointment nobody turns up to.\n\n#### Signature\n\n```http\nPOST /crm/reservations/slots (body) -> The available slots\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- No hold is placed. Handle the race between showing a slot and booking it.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The criteria to generate slots for.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["reservationDefinitionId","serviceDate"],"properties":{"reservationDefinitionId":{"type":"string","description":"REQUIRED — the `sk` of a `reservation_definition` record. Omitting it fails with 400 \"reservationDefinitionId required.\"","example":"6aa798c44f3cf920a2c64b07"},"serviceName":{"type":"string","description":"Must match a `services[].name` on the definition. Defaults to the first service.","example":"consultation"},"serviceDate":{"type":"string","description":"REQUIRED — the day to generate slots for.","example":"2026-09-16"},"slotIncrementMinutes":{"type":"integer","description":"Generate on a fixed interval instead of by service duration.","example":5},"partySize":{"type":"integer","example":2},"location":{"type":"string","description":"Narrow to a service point."},"servicePointId":{"type":"string","example":"sp_t7"},"startDate":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00.000Z"},"endDate":{"type":"string","format":"date-time","example":"2026-09-01T17:00:00.000Z"},"duration":{"type":"integer","description":"Slot length in minutes.","example":30}}},"example":{"servicePointId":"sp_t7","startDate":"2026-09-01T09:00:00.000Z","endDate":"2026-09-01T17:00:00.000Z","duration":30}}}},"responses":{"201":{"description":"The available slots","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/by-email/{email}/{reservationNumber}":{"get":{"operationId":"ReservationsController_getReservationByEmail","summary":"Get reservations by email","description":"A customer's reservations, keyed on their email address rather than an account — the self-service \"my bookings\" read.\n\n#### Signature\n\n```http\nGET /crm/reservations/by-email/{email}/{reservationNumber} (email: string, reservationNumber: string) -> The customer's reservations\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Keyed on email alone.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/reservations/cancel/{email}/{reservationNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"reservationNumber","required":true,"in":"path","description":"Reservation number. Omit to list all for the email.","schema":{"type":"string"},"example":"RES-4821"},{"name":"email","required":true,"in":"path","description":"Customer email address.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"The customer's reservations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A reservation (`reservation`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"reservationNumber":{"type":"string","example":"RES-4821"},"email":{"type":"string","example":"ada@example.com"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"servicePointId":{"type":"string","example":"sp_t7"},"status":{"type":"string","example":"confirmed"}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/cancel/{email}/{reservationNumber}":{"delete":{"operationId":"ReservationsController_cancelReservationEntry","summary":"Cancel a reservation","description":"Cancels a customer's reservation, matched on both email and reservation number so one alone is not enough. Prefer this to deletion — it frees the slot while keeping the record.\n\n#### Signature\n\n```http\nDELETE /crm/reservations/cancel/{email}/{reservationNumber} (email: string, reservationNumber: string) -> Cancellation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/reservations/delete/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"reservationNumber","required":true,"in":"path","description":"Reservation number.","schema":{"type":"string"},"example":"RES-4821"},{"name":"email","required":true,"in":"path","description":"Customer email address.","schema":{"type":"string"},"example":"ada@example.com"}],"responses":{"200":{"description":"Cancellation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/mine":{"get":{"operationId":"ReservationsController_getMyHostedReservations","summary":"My hosted bookings","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Paged reservations (`{ data, total }`)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Unauthorized — No signed-in user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/mine","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"],"description":"Reservations the signed-in staff member hosts — directly, or through a service they host. Up to 1000.\n\n#### Signature\n\n```http\nGET /crm/reservations/mine () -> Paged reservations (`{ data, total }`)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in user. | — |\n\nPlus the standard platform errors: `403`, `429`, `500`."}},"/crm/reservations/get/{id}":{"get":{"operationId":"ReservationsController_getReservationEntries","summary":"Get reservations","description":"Fetches one reservation by id, or lists them when the segment is omitted.\n\n#### Signature\n\n```http\nGET /crm/reservations/get/{id} (id: string) -> The reservation, or all reservations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/reservations/by-email/{email}/{reservationNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Reservation id. Omit to list.","schema":{"type":"string"},"example":"RES-4821"}],"responses":{"200":{"description":"The reservation, or all reservations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A reservation (`reservation`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"reservationNumber":{"type":"string","example":"RES-4821"},"email":{"type":"string","example":"ada@example.com"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"servicePointId":{"type":"string","example":"sp_t7"},"status":{"type":"string","example":"confirmed"}}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/get/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/delete/{id}":{"delete":{"operationId":"ReservationsController_deleteReservationEntry","summary":"Delete a reservation","description":"Deletes a reservation outright. Cancelling instead keeps the record and notifies the customer.\n\n#### Signature\n\n```http\nDELETE /crm/reservations/delete/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/reservations/cancel/{email}/{reservationNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Reservation id.","schema":{"type":"string"},"example":"RES-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/delete/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/create":{"post":{"operationId":"ReservationsController_createReservationEntry","summary":"Create a reservation","description":"Books a reservation. Availability is not held between generating slots and creating one, so a slot can be lost in between — handle a conflict on create.\n\n#### Signature\n\n```http\nPOST /crm/reservations/create (body) -> The created reservation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The `reservation_definition` it books against needs `workDays` as CAPITALISED full day names — `[\"Monday\",\"Tuesday\"]`. Lower-case or abbreviated names match nothing and that day silently generates no slots.\n- REQUIRES AUTH, unlike the rest of the booking flow. `definitions`, `slots`, `by-email` and `cancel` all answer anonymously; this returns 401 without a credential. A public page can therefore SHOW availability but cannot take a booking — call it through the browser runtime on a hosted page, or through your own server. Never by putting an operator token in a page.\n- Pass the slot's `startTime` and `endTime` back exactly as `slots` returned them. Rebuilding them from a local Date reintroduces the timezone the generator already resolved, and the booking lands an hour out twice a year.\n- A customer record is created from the `customer` object when the email is new, and reused when it is not.\n- Confirmation email is billed. An org without credit still books the reservation and sends nothing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/update`\n- `POST /crm/reservations/slots`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}}],"requestBody":{"description":"The reservation to create.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"properties":{"email":{"type":"string","example":"ada@example.com"},"servicePointId":{"type":"string","example":"sp_t7"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"}}}}},"example":{"data":{"email":"ada@example.com","servicePointId":"sp_t7","startDate":"2026-09-01T10:00:00.000Z","endDate":"2026-09-01T10:30:00.000Z"}}}}},"responses":{"201":{"description":"The created reservation","content":{"application/json":{"schema":{"type":"object","description":"A reservation (`reservation`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"reservationNumber":{"type":"string","example":"RES-4821"},"email":{"type":"string","example":"ada@example.com"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"servicePointId":{"type":"string","example":"sp_t7"},"status":{"type":"string","example":"confirmed"}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/update":{"post":{"operationId":"ReservationsController_updateReservationEntry","summary":"Update a reservation","description":"Updates a reservation's details — customer, party size, notes, status. Include the record `sk`. To move it to another time use `POST /crm/reservations/reschedule/{id}`.\n\n#### Signature\n\n```http\nPOST /crm/reservations/update (body) -> The updated reservation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A time sent here by staff is saved as given — no capacity check, no record of the old time, and the customer gets the generic \"updated\" message. Reschedule instead.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/reschedule/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}}],"requestBody":{"description":"The reservation to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f1a2b3c4d5e6f708192a3b","data":{"startDate":"2026-09-02T10:00:00.000Z"}}}}},"responses":{"201":{"description":"The updated reservation","content":{"application/json":{"schema":{"type":"object","description":"A reservation (`reservation`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"reservationNumber":{"type":"string","example":"RES-4821"},"email":{"type":"string","example":"ada@example.com"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"servicePointId":{"type":"string","example":"sp_t7"},"status":{"type":"string","example":"confirmed"}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/availability":{"post":{"operationId":"ReservationsController_getAvailability","summary":"Availability across a run of days","description":"What is open for a service over up to 31 days, in one call — the question a caller asks (\"anything Friday or Saturday?\").\n\nUnlike `slots`, full slots are returned too (`spotsAvailable: 0`), so a screen can show the shape of each day rather than a list with holes in it. A slot already past, or inside a blocked time, is not a slot and is left out. A day outside `workDays` comes back `closed: true` with no slots.\n\nCapacity is the definition's `spots`: how many bookings one slot holds. Any live booking that overlaps the slot at all takes a spot.\n\nTimes are UTC; format them in the returned `timezone` (the business's), not the viewer's.\n\n#### Signature\n\n```http\nPOST /crm/reservations/availability (body) -> Per-day slots, plus the first open one\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- No hold is placed. Handle the race between showing a slot and booking it.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/reschedule/{id}`\n- `POST /crm/reservations/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Per-day slots, plus the first open one","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reservationDefinitionId":"6aa798c44f3cf920a2c64b07","service":{"name":"consultation","duration":30,"price":0},"timezone":"America/New_York","from":"2026-09-21","to":"2026-09-27","nextAvailable":{"date":"2026-09-21","startTime":"2026-09-21T18:00:00.000Z","endTime":"2026-09-21T18:30:00.000Z","spotsTotal":2,"spotsAvailable":1},"days":[{"date":"2026-09-21","weekday":"Monday","closed":false,"openSlots":5,"slots":[{"startTime":"2026-09-21T18:00:00.000Z","endTime":"2026-09-21T18:30:00.000Z","spotsTotal":2,"spotsAvailable":1}]}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"],"requestBody":{"description":"What to look for.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reservationDefinitionId"],"properties":{"reservationDefinitionId":{"type":"string","description":"REQUIRED — the `sk` of a `reservation_definition` record.","example":"6aa798c44f3cf920a2c64b07"},"serviceName":{"type":"string","description":"A `services[].name` on the definition. Defaults to the first service.","example":"consultation"},"from":{"type":"string","description":"First day, as a calendar date at the business. Defaults to today; a past date is read as today.","example":"2026-09-21"},"days":{"type":"integer","description":"How many days, 1–31. Default 7.","example":7},"excludeReservationId":{"type":"string","description":"When moving a booking: its `sk`, so the seat it already holds reads as free.","example":"66f1a2b3c4d5e6f708192a3b"},"slotIncrementMinutes":{"type":"integer","description":"Interval-mode definitions only. Default 5.","example":15}}},"example":{"reservationDefinitionId":"6aa798c44f3cf920a2c64b07","serviceName":"consultation","from":"2026-09-21","days":7}}}}}},"/crm/reservations/stats":{"get":{"operationId":"ReservationsController_getReservationStats","summary":"Reservation figures","description":"Header figures for the Reservations screen, computed on the server: `total`, `today` (bookings on the current day in each booking's own business timezone), `upcoming` (live bookings still ahead) and `revenue` (sum of live bookings' price). Cancelled and no-show bookings count toward neither upcoming nor revenue. Staff only: a customer credential answers 401.\n\n#### Signature\n\n```http\nGET /crm/reservations/stats ()\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":""},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/slot-bookings":{"post":{"operationId":"ReservationsController_getSlotBookings","summary":"Bookings in a slot","description":"Who holds a slot — the live bookings that overlap the given time, earliest first, with the slot's capacity. Staff only: it names customers, so a customer credential answers 401.\n\n#### Signature\n\n```http\nPOST /crm/reservations/slot-bookings (body) -> `{ spotsTotal, spotsAvailable, bookings[] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/availability`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"`{ spotsTotal, spotsAvailable, bookings[] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/slot-bookings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"],"requestBody":{"description":"The slot, as `availability` returned it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reservationDefinitionId","startTime","endTime"],"properties":{"reservationDefinitionId":{"type":"string","example":"6aa798c44f3cf920a2c64b07"},"startTime":{"type":"string","format":"date-time","example":"2026-09-25T22:30:00.000Z"},"endTime":{"type":"string","format":"date-time","example":"2026-09-25T23:00:00.000Z"}}},"example":{"reservationDefinitionId":"6aa798c44f3cf920a2c64b07","startTime":"2026-09-25T22:30:00.000Z","endTime":"2026-09-25T23:00:00.000Z"}}}}}},"/crm/reservations/reschedule/{id}":{"post":{"operationId":"ReservationsController_rescheduleReservationEntry","summary":"Reschedule a reservation","description":"Moves a booking to another time. The end time is derived — the booking keeps its length, or takes the new service's duration when `service` changes with the move.\n\nThe new time is checked: not past, inside opening hours on a working day, not blocked, and with a spot free (the booking's own seat does not count against it). The move is appended to `data.rescheduleHistory` (from/to, when, who, reason), the customer is sent `reservation-rescheduled` showing old → new with a fresh calendar invite, and the reminders are re-cut for the new time.\n\nThe status is not changed — a moved booking is still pending or confirmed.\n\n#### Signature\n\n```http\nPOST /crm/reservations/reschedule/{id} (id: string, body) -> The moved reservation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A time that cannot take the booking answers 409 with `reason: \"slot_unavailable\"`, `problems: [{ code, message }]` (codes: `past`, `closed`, `outside_hours`, `blocked`, `full`) and `canOverride`. Show the message; staff may resend with `override: true`.\n- A customer may only move their own booking; anyone else's answers 404.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/availability`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Reservation `sk`.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The moved reservation","content":{"application/json":{"schema":{"type":"object","description":"A reservation (`reservation`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"reservationNumber":{"type":"string","example":"RES-4821"},"email":{"type":"string","example":"ada@example.com"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"servicePointId":{"type":"string","example":"sp_t7"},"status":{"type":"string","example":"confirmed"}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/reservations/reschedule/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"],"requestBody":{"description":"Where to move it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["startTime"],"properties":{"startTime":{"type":"string","format":"date-time","description":"REQUIRED — pass a slot's `startTime` back exactly as `availability` returned it.","example":"2026-09-25T22:30:00.000Z"},"service":{"type":"string","description":"Change the service with the move.","example":"consultation"},"reason":{"type":"string","description":"Shown to the customer and kept on the history entry.","example":"Customer called — running late from work"},"notify":{"type":"boolean","description":"Default true. False moves it silently.","example":true},"override":{"type":"boolean","description":"Staff only. Place the booking even though the time is full, blocked, closed or past. Ignored for a customer.","example":false}}},"example":{"startTime":"2026-09-25T22:30:00.000Z","reason":"Customer called — running late from work"}}}}}},"/crm/reservations/service-point/get/{id}":{"get":{"operationId":"ReservationsController_getServicePoints","summary":"Get service points","description":"The bookable service points — rooms, chairs, desks, staff. Omit `id` to list them all.\n\n#### Signature\n\n```http\nGET /crm/reservations/service-point/get/{id} (id: string) -> Service points\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/service-point/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":false,"in":"header","schema":{"type":"string"},"description":"Client context, used to tailor which service points are returned.","example":"kiosk/1.0"},{"name":"id","required":true,"in":"path","description":"Service point id. Omit to list.","schema":{"type":"string"},"example":"sp_t7"}],"responses":{"200":{"description":"Service points","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/service-point/delete/{id}":{"delete":{"operationId":"ReservationsController_deleteServicePoint","summary":"Delete a service point","description":"Deletes a service point. Reservations already booked against it are not moved or cancelled — check for them first, or they become unreachable.\n\n#### Signature\n\n```http\nDELETE /crm/reservations/service-point/delete/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Orphans any reservation still pointing at it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/reservations/service-point/get/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Service point id.","schema":{"type":"string"},"example":"sp_t7"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/service-point/create":{"post":{"operationId":"ReservationsController_createServicePoint","summary":"Create a service point","description":"Creates a bookable service point. Its availability rules are what the slot generator works from.\n\n#### Signature\n\n```http\nPOST /crm/reservations/service-point/create (body) -> The created service point\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/service-point/update`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The service point to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"data":{"name":"Consulting room 2","capacity":1}}}}},"responses":{"201":{"description":"The created service point","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/service-point/update":{"post":{"operationId":"ReservationsController_updateServicePoint","summary":"Update a service point","description":"Updates a service point. Changing its availability affects future slot generation but does not move reservations already booked against it.\n\n#### Signature\n\n```http\nPOST /crm/reservations/service-point/update (body) -> The updated service point\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing reservations are not revalidated against new availability rules.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/reservations/service-point/delete/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The service point to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f1a2b3c4d5e6f708192a3b","data":{"name":"Consulting room 2 (accessible)"}}}}},"responses":{"201":{"description":"The updated service point","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/meeting/token":{"get":{"operationId":"ReservationsController_getMeetingToken","summary":"Get a meeting token","description":"Issues a token for the caller to join a video meeting. Tokens are short-lived — fetch one when joining rather than storing it.\n\n#### Signature\n\n```http\nGET /crm/reservations/meeting/token () -> A meeting token\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- The token grants meeting access — treat it as a credential.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/meeting/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"A meeting token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/meeting/create":{"post":{"operationId":"ReservationsController_createMeeting","summary":"Create a meeting room","description":"Creates a video meeting room, typically for a reservation that is held remotely.\n\n#### Signature\n\n```http\nPOST /crm/reservations/meeting/create (body) -> The created meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/reservations/meeting/validate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The meeting to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Consultation with Ada","reservationId":"RES-4821"}}}},"responses":{"201":{"description":"The created meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/meeting/validate":{"post":{"operationId":"ReservationsController_validateMeeting","summary":"Validate a meeting","description":"Checks that a meeting exists and the caller may join it — the pre-flight before showing a join button.\n\n#### Signature\n\n```http\nPOST /crm/reservations/meeting/validate (body) -> Whether the meeting is valid and joinable\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/reservations/meeting/token`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The meeting to validate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"meetingId":"MTG-4821"}}}},"responses":{"201":{"description":"Whether the meeting is valid and joinable","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/reservations/send-reminder/{reservationId}":{"post":{"operationId":"ReservationsController_sendReservationReminder","summary":"Send a reservation reminder","description":"Sends the customer a reminder about an upcoming reservation. Sends on every call — there is no once-only guard, so repeated calls repeat the reminder.\n\n#### Signature\n\n```http\nPOST /crm/reservations/send-reminder/{reservationId} (reservationId: string) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/reservations/get/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"reservationId","required":true,"in":"path","description":"Reservation id.","schema":{"type":"string"},"example":"RES-4821"}],"requestBody":{"required":false,"description":"Notification details (optional - uses reservation definition notifications if not provided)","content":{"application/json":{"schema":{"type":"object","properties":{"notificationIndex":{"type":"number","description":"Index of notification to send from reservation definition (default: 0)"}}}}}},"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Reservations"]}},"/crm/tickets/chat-request":{"post":{"operationId":"TicketsController_createChatRequest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"authorization","required":true,"in":"header","schema":{"type":"string"},"description":"Bearer guest token from the chat widget."}],"responses":{"201":{"description":"The created ticket","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A name, valid email, message and widget are required — The guest has no name or valid email, or `description` / `configId` is missing or too long.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A name, valid email, message and widget are required","path":"/crm/tickets/chat-request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Guest authentication is required — No Bearer token (\"Guest authentication is invalid or expired\" when it does not verify).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Guest authentication is required","path":"/crm/tickets/chat-request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This guest does not belong to this organization — The token is not a guest customer of this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This guest does not belong to this organization","path":"/crm/tickets/chat-request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"summary":"Leave a request from the chat widget","description":"For a chat widget that is away: turns the visitor's message into a ticket. Only a **guest** session token from the widget is accepted (`Authorization: Bearer <guest token>`, a guest customer of this org). Name, email and phone come from the guest session; the caller can only send `data.description` and `data.configId`, and the widget must have its away form (`offline.showForm`) switched on. The ticket is created `new`, priority `medium`, source `chat-widget`.\n\n#### Signature\n\n```http\nPOST /crm/tickets/chat-request (body) -> The created ticket\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | GUEST_REQUIRED | Guest authentication is required | No Bearer token (\"Guest authentication is invalid or expired\" when it does not verify). | — |\n| `403` | WRONG_ORG | This guest does not belong to this organization | The token is not a guest customer of this org. | — |\n| `400` | FIELDS_REQUIRED | A name, valid email, message and widget are required | The guest has no name or valid email, or `description` / `configId` is missing or too long. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"description":{"type":"string","maxLength":10000},"configId":{"type":"string"},"reportedByEmail":{"type":"string","description":"Optional; must equal the guest email."}}}}},"example":{"data":{"description":"Do you deliver on Sundays?","configId":"support-widget"}}}}}}},"/crm/tickets/collections":{"get":{"operationId":"TicketsController_getTicketCollections","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Ticket collection definitions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/tickets/collections","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"summary":"Get ticket collections","description":"The ticket collection definitions for the org — the categories and schemas tickets are filed under. Read this to build a ticket form generically.\n\n#### Signature\n\n```http\nGET /crm/tickets/collections () -> Ticket collection definitions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/create`"}},"/crm/tickets/list":{"get":{"operationId":"TicketsController_listForStaff","summary":"List tickets (staff)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"One status or a comma-separated list."},{"name":"priority","required":false,"in":"query","schema":{"type":"string"}},{"name":"assignTo","required":false,"in":"query","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"inWorkflow","required":false,"in":"query","schema":{"type":"string","enum":["true","1"]}},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":25},"description":"Max 200."}],"responses":{"200":{"description":"{ data, total, page, pageSize }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Authentication for this organization is required — The caller is not a user or customer of this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Authentication for this organization is required","path":"/crm/tickets/list","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"A signed-in customer is required — A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"A signed-in customer is required","path":"/crm/tickets/list","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"description":"Staff only. Tickets newest first, paged and filtered by the server, each with `workflowInfo` (its workflow, stage and task) when it is on one. `search` matches name, title, reporter, reporter email and description. `inWorkflow=true` keeps tickets that have a workflow task.\n\n#### Signature\n\n```http\nGET /crm/tickets/list (status?: string, priority?: string, assignTo?: string, search?: string, inWorkflow?: string, page?: integer, pageSize?: integer) -> { data, total, page, pageSize }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | ORG_AUTH_REQUIRED | Authentication for this organization is required | The caller is not a user or customer of this org. | — |\n| `403` | STAFF_ONLY | A signed-in customer is required | A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/crm/tickets/workflows":{"get":{"operationId":"TicketsController_workflowsOverview","summary":"Ticket workflows","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ data, total, resolves }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Authentication for this organization is required — The caller is not a user or customer of this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Authentication for this organization is required","path":"/crm/tickets/workflows","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"A signed-in customer is required — A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"A signed-in customer is required","path":"/crm/tickets/workflows","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"description":"Staff only. The workflows tickets run through, each with what is in flight in it, plus `resolves` — how the org decides which workflow a new ticket takes.\n\n#### Signature\n\n```http\nGET /crm/tickets/workflows () -> { data, total, resolves }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | ORG_AUTH_REQUIRED | Authentication for this organization is required | The caller is not a user or customer of this org. | — |\n| `403` | STAFF_ONLY | A signed-in customer is required | A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/crm/tickets/workflow/{id}":{"get":{"operationId":"TicketsController_workflowFor","summary":"Where a ticket stands in its workflow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket sk."}],"responses":{"200":{"description":"Workflow info, or null","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Authentication for this organization is required — The caller is not a user or customer of this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Authentication for this organization is required","path":"/crm/tickets/workflow/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"A signed-in customer is required — A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"A signed-in customer is required","path":"/crm/tickets/workflow/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Ticket not found — No ticket with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/workflow/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"description":"Staff only. The ticket's workflow position (workflow, stage, task, stage history), or `null` when it is on none.\n\n#### Signature\n\n```http\nGET /crm/tickets/workflow/{id} (id: string) -> Workflow info, or null\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | ORG_AUTH_REQUIRED | Authentication for this organization is required | The caller is not a user or customer of this org. | — |\n| `403` | STAFF_ONLY | A signed-in customer is required | A customer or guest calls a staff route (the message is the platform's; staff routes refuse every non-user). | — |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket with that id. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/crm/tickets/get-by-email/{email}/{number}":{"get":{"operationId":"TicketsController_getTicketByEmail","summary":"Get tickets by email","description":"Fetches a customer's tickets by their email address rather than through their account — how a support portal shows someone their tickets when they have no login.\n\nThe email is the only key, so anyone who knows an address can list that person's tickets. Rate-limit any public surface built on it.\n\n#### Signature\n\n```http\nGET /crm/tickets/get-by-email/{email}/{number} (email: string, number: string) -> The customer's tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Keyed on email alone — treat the address as the credential it effectively is.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/email/create/{email}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"email","required":true,"in":"path","description":"Customer email address.","schema":{"type":"string"},"example":"ada@example.com"},{"name":"number","required":true,"in":"path","description":"Ticket number. Omit to list all for the email.","schema":{"type":"string"},"example":"TKT-4821"}],"responses":{"200":{"description":"The customer's tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/email/create/{email}":{"post":{"operationId":"TicketsController_createTicketByEmail","summary":"Create a ticket for an email address","description":"Raises a ticket attributed to an email address rather than to a signed-in account — the intake path for a contact form or an inbound support email.\n\n#### Signature\n\n```http\nPOST /crm/tickets/email/create/{email} (email: string, body) -> The created ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/tickets/delete-by-email/{email}/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Customer email address.","example":"ada@example.com"}],"requestBody":{"description":"The ticket to raise.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}},"example":{"data":{"title":"Cannot log in after password reset","description":"Reset link works but sign-in still fails"}}}}},"responses":{"201":{"description":"The created ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/delete-by-email/{email}/{id}":{"delete":{"operationId":"TicketsController_deleteTicketByEmail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Customer email address.","example":"ada@example.com"},{"name":"id","in":"path","required":true,"description":"Ticket id.","schema":{"type":"string"},"example":"TKT-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"summary":"Delete a ticket by email","description":"Deletes a ticket belonging to an email address. Both must match, so an id alone is not enough to delete someone else's ticket through this route.\n\n#### Signature\n\n```http\nDELETE /crm/tickets/delete-by-email/{email}/{id} (email: string, id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/tickets/get-by-email/{email}/{number}`"}},"/crm/tickets/get/{number}":{"get":{"operationId":"TicketsController_getTickets","summary":"Get tickets","description":"Fetches one ticket by number, or lists them all when the segment is omitted. Scoped to what the caller may see.\n\n#### Signature\n\n```http\nGET /crm/tickets/get/{number} (number: string, status?: string, enrich?: boolean) -> The ticket, or all tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/tickets/get-by-email/{email}/{number}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"number","required":true,"in":"path","description":"Ticket number. Omit to list.","schema":{"type":"string"},"example":"TKT-4821"},{"name":"status","required":false,"in":"query","description":"Filter by status.","schema":{"type":"string"}},{"name":"enrich","required":false,"in":"query","description":"true to resolve linked records.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The ticket, or all tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/tickets/get/{number}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/delete/{id}":{"delete":{"operationId":"TicketsController_deleteTickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","in":"path","required":true,"description":"Ticket id.","schema":{"type":"string"},"example":"TKT-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/tickets/delete/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"summary":"Delete a ticket","description":"Deletes a ticket and its history. Closing it instead preserves the record of what was asked and how it was resolved.\n\n#### Signature\n\n```http\nDELETE /crm/tickets/delete/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /crm/tickets/status/{ticketId}`"}},"/crm/tickets/create":{"post":{"operationId":"TicketsController_createTicket","summary":"Create a ticket","description":"Raises a support ticket on behalf of the signed-in caller. `title` is the one required field — everything else can be filled in later.\n\nTwo further create endpoints exist: `POST /crm/tickets/create-ticket` and `POST /crm/tickets/email/create/{email}`. They differ in who the ticket is attributed to.\n\n#### Signature\n\n```http\nPOST /crm/tickets/create (body) -> The created ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n| `400` | TITLE_REQUIRED | title is required | `title` is missing from the ticket data. | Every ticket needs a title; the rest is optional. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/create-ticket`\n- `POST /crm/tickets/email/create/{email}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The ticket to raise.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}},"required":["title"]}}},"example":{"data":{"title":"Cannot log in after password reset","description":"Reset link works but sign-in still fails","priority":"high"}}}}},"responses":{"201":{"description":"The created ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"400":{"description":"title is required — `title` is missing from the ticket data.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"title is required","path":"/crm/tickets/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/tickets/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/report-ide-issue":{"post":{"operationId":"TicketsController_reportIDEIssue","summary":"Report an IDE issue","description":"Raises a ticket from the IDE integration, tagged so these reports can be triaged separately from ordinary support traffic.\n\n#### Signature\n\n```http\nPOST /crm/tickets/report-ide-issue (body) -> The created ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The issue report.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}},"example":{"data":{"title":"Autocomplete stops responding on large files","description":"Reproduces on files over 5k lines"}}}}},"responses":{"201":{"description":"The created ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/tickets/report-ide-issue","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/create-ticket":{"post":{"operationId":"TicketsController_createTicketPost","summary":"Create a ticket (alternate)","description":"A second create endpoint, taking the ticket body without the request context the primary `create` uses. Prefer `POST /crm/tickets/create` unless you specifically need this one.\n\n#### Signature\n\n```http\nPOST /crm/tickets/create-ticket (body) -> The created ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The ticket to raise.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}},"example":{"data":{"title":"Cannot log in","priority":"high"}}}}},"responses":{"201":{"description":"The created ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/tickets/create-ticket","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/update":{"post":{"operationId":"TicketsController_updateTickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"401":{"description":"Unauthorized — No signed-in customer or user could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Unauthorized","path":"/crm/tickets/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"summary":"Update a ticket","description":"Updates a ticket's fields. Status, assignment and priority each have a dedicated endpoint that records the change properly — use those rather than writing the field here.\n\n#### Signature\n\n```http\nPOST /crm/tickets/update (body) -> The updated ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Setting `status` here bypasses the resolution capture that `PATCH status/{ticketId}` performs.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /crm/tickets/status/{ticketId}`","requestBody":{"description":"The ticket to update, including its `sk`.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f1a2b3c4d5e6f708192a3b","data":{"description":"Updated with reproduction steps"}}}}}}},"/crm/tickets/reply/{ticketId}":{"post":{"operationId":"TicketsController_replyToTicket","summary":"Reply to a ticket","description":"Sends a reply on a ticket, by email. `to` is required — a reply with no recipient is refused rather than silently recorded.\n\nSet `isStaffReply` to mark it as coming from support rather than the customer. Use `templateId` to send a templated reply instead of raw `html` or `text`.\n\n#### Signature\n\n```http\nPOST /crm/tickets/reply/{ticketId} (ticketId: string, body) -> The sent reply\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `to` is not defaulted from the ticket's reporter. Send it explicitly.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n| `400` | RECIPIENT_REQUIRED | Recipient (to) is required | `to` is missing. | A reply must name a recipient — it is not inferred from the ticket. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/tickets/messages/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-4821"}],"responses":{"201":{"description":"The sent reply","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Recipient (to) is required — `to` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Recipient (to) is required","path":"/crm/tickets/reply/{ticketId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/reply/{ticketId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"requestBody":{"description":"The reply to send.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["to"],"properties":{"to":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Recipient(s). Required.","example":"ada@example.com"},"from":{"type":"string","description":"Sender address. Defaults to the org support address."},"subject":{"type":"string","example":"Re: Cannot log in after password reset"},"html":{"type":"string","description":"HTML body."},"text":{"type":"string","description":"Plain-text body."},"templateId":{"type":"string","description":"Send a templated reply instead of raw content.","example":"ticket-reply"},"deliveryType":{"type":"string","description":"Delivery channel.","example":"email"},"isStaffReply":{"type":"boolean","description":"Marks the reply as from support rather than the customer.","example":true}}},"examples":{"staff":{"summary":"Staff reply","value":{"to":"ada@example.com","subject":"Re: Cannot log in","html":"<p>Could you try clearing your cookies?</p>","isStaffReply":true}},"templated":{"summary":"Templated reply","value":{"to":"ada@example.com","templateId":"ticket-reply","isStaffReply":true}}}}}}}},"/crm/tickets/messages/{ticketId}":{"get":{"operationId":"TicketsController_getTicketMessages","summary":"Get ticket messages","description":"The customer-facing conversation on a ticket — everything sent to and received from the reporter. Internal comments are separate.\n\n#### Signature\n\n```http\nGET /crm/tickets/messages/{ticketId} (ticketId: string) -> The ticket's messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/tickets/comments/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-4821"}],"responses":{"200":{"description":"The ticket's messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/messages/{ticketId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/comments/{ticketId}":{"post":{"operationId":"TicketsController_addInternalComment","summary":"Add an internal comment","description":"Adds a comment visible only to staff. This is the place for triage notes and internal discussion — unlike a reply, nothing is sent to the customer.\n\n#### Signature\n\n```http\nPOST /crm/tickets/comments/{ticketId} (ticketId: string, body) -> The added comment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/reply/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-4821"}],"responses":{"201":{"description":"The added comment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/comments/{ticketId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"requestBody":{"description":"The comment.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Internal note. Not shown to the customer.","example":"Third report this week — likely the session cookie change"}}},"example":{"message":"Third report this week — likely the session cookie change"}}}}},"get":{"operationId":"TicketsController_getTicketComments","summary":"Get internal comments","description":"The staff-only comments on a ticket. Keep these out of any customer-facing view — they are separate from `messages` precisely so they are not shown.\n\n#### Signature\n\n```http\nGET /crm/tickets/comments/{ticketId} (ticketId: string) -> Internal comments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/comments/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-4821"}],"responses":{"200":{"description":"Internal comments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/comments/{ticketId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/tickets/status/{ticketId}":{"patch":{"operationId":"TicketsController_changeStatus","summary":"Change a ticket status","description":"Moves a ticket to a new status. When closing one, supply `resolution` — the summary and root cause are what makes a closed ticket useful later, and this is the only endpoint that captures them.\n\n#### Signature\n\n```http\nPATCH /crm/tickets/status/{ticketId} (ticketId: string, body) -> The updated ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `POST /crm/tickets/update` can also set a status, but does not capture a resolution — use this endpoint when closing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /crm/tickets/assign/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-4821"}],"responses":{"200":{"description":"The updated ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/status/{ticketId}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"requestBody":{"description":"The new status, and the resolution when closing.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"New status.","example":"resolved"},"resolution":{"type":"object","description":"Recorded when resolving.","properties":{"summary":{"type":"string","example":"Cleared stale session cookie"},"rootCause":{"type":"string","example":"Session cookie not invalidated on password reset"}}}}},"examples":{"resolve":{"summary":"Resolve with a root cause","value":{"status":"resolved","resolution":{"summary":"Cleared stale session cookie","rootCause":"Session cookie not invalidated on password reset"}}},"reopen":{"summary":"Reopen","value":{"status":"open"}}}}}}}},"/crm/tickets/assign/{ticketId}":{"patch":{"operationId":"TicketsController_reassignTicket","summary":"Reassign a ticket","description":"Assigns a ticket to a different agent, with an optional handover note.\n\n#### Signature\n\n```http\nPATCH /crm/tickets/assign/{ticketId} (ticketId: string, body) -> The updated ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /crm/tickets/priority/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-4821"}],"responses":{"200":{"description":"The updated ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/assign/{ticketId}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"requestBody":{"description":"Who to assign it to.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignTo"],"properties":{"assignTo":{"type":"string","example":"support@acme.com"},"note":{"type":"string","description":"Handover note, recorded on the ticket.","example":"Needs someone from the auth team"}}},"example":{"assignTo":"support@acme.com","note":"Needs someone from the auth team"}}}}}},"/crm/tickets/priority/{ticketId}":{"patch":{"operationId":"TicketsController_changePriority","summary":"Change a ticket priority","description":"Sets a ticket's priority, which drives queue ordering and any SLA attached to it.\n\n#### Signature\n\n```http\nPATCH /crm/tickets/priority/{ticketId} (ticketId: string, body) -> The updated ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TICKET_NOT_FOUND | Ticket not found | No ticket has that id. | Check the id with `GET /crm/tickets/get`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/bulk-update`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-4821"}],"responses":{"200":{"description":"The updated ticket","content":{"application/json":{"schema":{"type":"object","description":"A support ticket (`ticket`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"number":{"type":"string","description":"Human ticket number.","example":"TKT-4821"},"title":{"type":"string","description":"Required on create.","example":"Cannot log in after password reset"},"description":{"type":"string"},"status":{"type":"string","example":"open"},"priority":{"type":"string","example":"high"},"assignedTo":{"type":"string","example":"support@acme.com"},"email":{"type":"string","description":"Reporter email — the key the by-email routes use.","example":"ada@example.com"},"resolution":{"type":"object","description":"Filled in when the ticket is closed.","properties":{"summary":{"type":"string"},"rootCause":{"type":"string"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/crm/tickets/priority/{ticketId}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"requestBody":{"description":"The new priority.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["priority"],"properties":{"priority":{"type":"string","example":"urgent"}}},"example":{"priority":"urgent"}}}}}},"/crm/tickets/bulk-update":{"post":{"operationId":"TicketsController_bulkUpdate","summary":"Update several tickets","description":"Applies the same status, priority or assignment to many tickets at once — for clearing a queue or handing over a shift.\n\nBecause it goes through the bulk path, no resolution is captured: closing tickets this way records the status but not why.\n\n#### Signature\n\n```http\nPOST /crm/tickets/bulk-update (body) -> The bulk update result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No resolution is recorded. Close tickets individually where the root cause matters.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /crm/tickets/status/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The bulk update result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"],"requestBody":{"description":"Which tickets, and what to change.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["ticketIds"],"properties":{"ticketIds":{"type":"array","items":{"type":"string"},"example":["TKT-4821","TKT-4822"]},"status":{"type":"string","example":"closed"},"priority":{"type":"string","example":"low"},"assignTo":{"type":"string","example":"support@acme.com"}}},"examples":{"reassign":{"summary":"Hand a queue to another agent","value":{"ticketIds":["TKT-4821","TKT-4822"],"assignTo":"support@acme.com"}},"closeOut":{"summary":"Close a batch","value":{"ticketIds":["TKT-4821","TKT-4822"],"status":"closed"}}}}}}}},"/crm/tickets/canned-responses":{"get":{"operationId":"TicketsController_getCannedResponses","summary":"Get canned responses","description":"The saved reply templates available to agents, for answering common questions consistently.\n\n#### Signature\n\n```http\nGET /crm/tickets/canned-responses () -> Canned responses\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/tickets/reply/{ticketId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Canned responses","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Tickets"]}},"/crm/communications/sms":{"get":{"operationId":"CommunicationsController_getSmsMessages","summary":"List SMS messages","description":"SMS messages sent and received. Distinct from `twilio-sms`, which reads from the provider directly.\n\n#### Signature\n\n```http\nGET /crm/communications/sms (startDate?: string, endDate?: string) -> SMS messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/twilio-sms`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"offset","required":false,"in":"query","description":"Offset for pagination","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Number of results to return","schema":{"type":"number"}},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-01"},{"name":"status","required":false,"in":"query","description":"Filter by status (sent, delivered, failed)","schema":{}},{"name":"to","required":false,"in":"query","description":"Filter by recipient phone number","schema":{}},{"name":"from","required":false,"in":"query","description":"Filter by sender phone number","schema":{}}],"responses":{"200":{"description":"SMS messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/recordings":{"get":{"operationId":"CommunicationsController_getVoiceRecordings","summary":"List call recordings","description":"The recordings held for calls.\n\nCall recordings are personal data and in many jurisdictions require consent from both parties. Restrict access and retention accordingly — this endpoint enforces neither.\n\n#### Signature\n\n```http\nGET /crm/communications/recordings (startDate?: string, endDate?: string) -> Call recordings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Recordings are sensitive personal data. No consent or retention policy is applied here.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/recordings/{recordingSid}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"offset","required":false,"in":"query","description":"Offset for pagination","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Number of results to return","schema":{"type":"number"}},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-01"},{"name":"status","required":false,"in":"query","description":"Filter by status","schema":{}},{"name":"reference","required":false,"in":"query","description":"Filter by call SID reference","schema":{}}],"responses":{"200":{"description":"Call recordings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/twilio-sms":{"get":{"operationId":"CommunicationsController_getSmsMessagesLive","summary":"List SMS from Twilio","description":"Reads SMS records **directly from Twilio** rather than from the platform store. Use it to reconcile — to see what the provider recorded independently of what was saved here.\n\nThis is a live provider call and counts against Twilio's rate limits.\n\n#### Signature\n\n```http\nGET /crm/communications/twilio-sms (startDate?: string, endDate?: string) -> SMS records as Twilio holds them\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The shape is Twilio's, not the platform's.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/sms`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"limit","required":false,"in":"query","description":"Number of results to return","schema":{"type":"number"}},{"name":"to","required":false,"in":"query","description":"Filter by recipient phone number","schema":{}},{"name":"from","required":false,"in":"query","description":"Filter by sender phone number","schema":{}},{"name":"number","required":false,"in":"query","description":"Phone number — merges inbound + outbound","schema":{}},{"name":"startDate","in":"query","required":false,"schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","in":"query","required":false,"schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"SMS records as Twilio holds them","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/voicemails":{"get":{"operationId":"CommunicationsController_getVoicemails","summary":"List voicemails","description":"Voicemail messages left for the org.\n\n#### Signature\n\n```http\nGET /crm/communications/voicemails (startDate?: string, endDate?: string) -> Voicemails\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/recordings`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"limit","required":false,"in":"query","description":"Number of results to return","schema":{"type":"number"}},{"name":"callSid","required":false,"in":"query","description":"Filter by call SID","schema":{}},{"name":"startDate","in":"query","required":false,"schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","in":"query","required":false,"schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Voicemails","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/recordings/{recordingSid}":{"get":{"operationId":"CommunicationsController_getRecording","summary":"Get a call recording","description":"Metadata for one recording — duration, participants and whether it has been transcribed.\n\n#### Signature\n\n```http\nGET /crm/communications/recordings/{recordingSid} (recordingSid: string) -> Recording metadata\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Recording not found | No recording has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/recordings/{recordingSid}/audio`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"recordingSid","required":true,"in":"path","description":"Provider recording id.","schema":{"type":"string"},"example":"RE9k2m4h1p7q"}],"responses":{"200":{"description":"Recording metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Recording not found — No recording has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Recording not found","path":"/crm/communications/recordings/{recordingSid}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/recordings/{recordingSid}/audio":{"get":{"operationId":"CommunicationsController_getRecordingAudio","summary":"Get recording audio","description":"Streams the audio of a call recording. Handle it as sensitive personal data — do not cache it in a shared location or expose the URL more widely than the recording itself.\n\n#### Signature\n\n```http\nGET /crm/communications/recordings/{recordingSid}/audio (recordingSid: string) -> The recording audio\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Recording not found | No recording has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/communications/recordings/transcribe`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"recordingSid","required":true,"in":"path","description":"Provider recording id.","schema":{"type":"string"},"example":"RE9k2m4h1p7q"}],"responses":{"200":{"description":"The recording audio","content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Recording not found — No recording has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Recording not found","path":"/crm/communications/recordings/{recordingSid}/audio","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/calls":{"get":{"operationId":"CommunicationsController_getCallLogs","summary":"List call logs","description":"Inbound and outbound call records, with duration and outcome.\n\n#### Signature\n\n```http\nGET /crm/communications/calls (startDate?: string, endDate?: string) -> Call logs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/recordings`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"limit","required":false,"in":"query","description":"Number of results to return","schema":{"type":"number"}},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-01"},{"name":"status","required":false,"in":"query","description":"Filter by call status","schema":{}},{"name":"to","required":false,"in":"query","description":"Filter by recipient phone number","schema":{}},{"name":"from","required":false,"in":"query","description":"Filter by caller phone number","schema":{}}],"responses":{"200":{"description":"Call logs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications":{"get":{"operationId":"CommunicationsController_getAllCommunications","summary":"List all communications","description":"Every communication across channels — SMS, calls and voicemails together. The unified telephony view.\n\n#### Signature\n\n```http\nGET /crm/communications (startDate?: string, endDate?: string, page?: integer, pageSize?: integer) -> Communications\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/stats`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"offset","required":false,"in":"query","description":"Offset for pagination","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Number of results to return","schema":{"type":"number"}},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-01"},{"name":"to","required":false,"in":"query","description":"Filter by recipient phone number","schema":{}},{"name":"from","required":false,"in":"query","description":"Filter by sender phone number","schema":{}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Communications","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/stats":{"get":{"operationId":"CommunicationsController_getCommunicationStats","summary":"Get communication statistics","description":"Aggregate telephony figures — volumes by channel and direction over a period.\n\n#### Signature\n\n```http\nGET /crm/communications/stats (startDate?: string, endDate?: string) -> Communication statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-01"}],"responses":{"200":{"description":"Communication statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/recordings/transcribe":{"post":{"operationId":"CommunicationsController_requestTranscription","summary":"Request a transcription","description":"Requests a transcription of a call recording. Transcription is asynchronous and usually billed per minute — request it deliberately rather than for every call.\n\n#### Signature\n\n```http\nPOST /crm/communications/recordings/transcribe (body) -> The transcription request result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Asynchronous — poll the recording for the finished transcript rather than expecting it in this response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Recording not found | No recording has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/recordings/{recordingSid}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which recording to transcribe.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["recordingSid"],"properties":{"recordingSid":{"type":"string","example":"RE9k2m4h1p7q"}}},"example":{"recordingSid":"RE9k2m4h1p7q"}}}},"responses":{"201":{"description":"The transcription request result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Recording not found — No recording has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Recording not found","path":"/crm/communications/recordings/transcribe","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/twilio/setup":{"post":{"operationId":"CommunicationsController_setupTwilioNumber","summary":"Set up a Twilio number","description":"Configures a phone number for the org — either claiming one you already hold, or **purchasing a new one** when `purchaseNew` is set.\n\nPurchasing incurs a real, recurring charge from Twilio. Use `GET /crm/communications/twilio/available-numbers` to see what is available first, and set `purchaseNew` deliberately.\n\n#### Signature\n\n```http\nPOST /crm/communications/twilio/setup (body) -> The configured number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `purchaseNew: true` spends money and creates an ongoing monthly cost.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/twilio/available-numbers`\n- `GET /crm/communications/twilio/verify`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which number to configure, or whether to buy one.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phoneNumber":{"type":"string","description":"An existing number to configure.","example":"+15551234567"},"purchaseNew":{"type":"boolean","default":false,"description":"**Buys a new number**, incurring a recurring charge.","example":false},"areaCode":{"type":"string","description":"Preferred area code when purchasing.","example":"415"}}},"examples":{"existing":{"summary":"Configure a number you already have","value":{"phoneNumber":"+15551234567"}},"purchase":{"summary":"Buy a new number","description":"Incurs a recurring charge.","value":{"purchaseNew":true,"areaCode":"415"}}}}}},"responses":{"200":{"description":"Successfully setup Twilio phone number","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"phoneNumber":{"type":"string"},"webhooksConfigured":{"type":"boolean"},"message":{"type":"string"}}}}}},"201":{"description":"The configured number","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/twilio/verify":{"get":{"operationId":"CommunicationsController_verifyTwilioSetup","summary":"Verify Twilio setup","description":"Checks that the org's Twilio configuration is complete and working. Run this after setup, and first when messages or calls stop arriving.\n\n#### Signature\n\n```http\nGET /crm/communications/twilio/verify () -> Verification result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/communications/twilio/setup`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/twilio/available-numbers":{"get":{"operationId":"CommunicationsController_getAvailablePhoneNumbers","summary":"List available phone numbers","description":"Numbers available to purchase from Twilio, optionally filtered by area code. Listing costs nothing — buying happens through `twilio/setup`.\n\n#### Signature\n\n```http\nGET /crm/communications/twilio/available-numbers (areaCode?: string, country?: string) -> Available numbers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only — no number is reserved by listing it, so one shown here can be taken before you buy it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/communications/twilio/setup`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"limit","required":false,"in":"query","description":"Number of results to return (default: 20)","schema":{"type":"number"}},{"name":"contains","required":false,"in":"query","description":"Numbers containing specific digits (e.g., \"555\")","schema":{}},{"name":"state","required":false,"in":"query","description":"State/region code to search in (e.g., \"TX\", \"CA\", \"NY\")","schema":{}},{"name":"zipCode","required":false,"in":"query","description":"ZIP/Postal code to search in (e.g., \"75252\", \"10001\")","schema":{}},{"name":"city","required":false,"in":"query","description":"City name to search in (e.g., \"Plano\", \"Austin\")","schema":{}},{"name":"areaCode","required":false,"in":"query","description":"Preferred area code.","schema":{"type":"string"},"example":"415"},{"name":"countryCode","required":false,"in":"query","description":"Country code (default: US)","schema":{}},{"name":"country","in":"query","required":false,"description":"ISO country code.","schema":{"type":"string"},"example":"US"}],"responses":{"200":{"description":"Available numbers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/communications/twilio/system-phone":{"get":{"operationId":"CommunicationsController_getSystemPhone","summary":"Get the system phone number","description":"The platform-level phone number, as opposed to the org's own. Used where a message must come from the platform rather than the tenant.\n\n#### Signature\n\n```http\nGET /crm/communications/twilio/system-phone () -> The system phone number\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/communications/twilio/verify`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The system phone number","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Communications"]}},"/crm/ads/campaigns":{"post":{"operationId":"AdsController_createCampaign","parameters":[],"responses":{"201":{"description":"The created campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"The ad platform rejected the request. — The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The ad platform rejected the request.","path":"/crm/ads/campaigns","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create an ad campaign","description":"Creates a campaign. It starts as a draft — creating does not spend money or push anything to a platform; `POST /crm/ads/campaigns/launch/{campaignId}` does that.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns (body) -> The created campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Creating is free — nothing reaches a platform until it is launched.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLATFORM_ERROR | The ad platform rejected the request. | The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec. | The platform's own message is passed through. Check the integration is still connected with `GET /crm/ads/integrations/status`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/launch/{campaignId}`","tags":["CRM · Ads"],"requestBody":{"description":"The campaign to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}},"example":{"name":"Summer sale — retargeting","platforms":["facebook","instagram"],"budget":5000,"objective":"conversions","startDate":"2026-09-01T00:00:00.000Z","endDate":"2026-09-30T23:59:59.000Z"}}}}},"get":{"operationId":"AdsController_getCampaigns","parameters":[{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"]},"example":"active"},{"name":"platform","required":false,"in":"query","schema":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"example":"facebook"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","default":20},"description":"Page size. Note this is `limit`, not `pageSize` as elsewhere on the platform.","example":20}],"responses":{"200":{"description":"A page of campaigns","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}},"total":{"type":"integer","example":24}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List ad campaigns","description":"Lists campaigns with optional filters and paging.\n\n#### Signature\n\n```http\nGET /crm/ads/campaigns (status?: string, platform?: string, page?: integer, limit?: integer) -> A page of campaigns\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Paging uses `limit`, not the platform-standard `pageSize`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/campaigns/{campaignId}`","tags":["CRM · Ads"]}},"/crm/ads/campaigns/{campaignId}":{"get":{"operationId":"AdsController_getCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"200":{"description":"The campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/{campaignId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an ad campaign","description":"Fetches one campaign with its budget, targeting and current status.\n\n#### Signature\n\n```http\nGET /crm/ads/campaigns/{campaignId} (campaignId: string) -> The campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/campaigns/metrics/{campaignId}`","tags":["CRM · Ads"]},"put":{"operationId":"AdsController_updateCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"200":{"description":"The updated campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/{campaignId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update an ad campaign","description":"Updates a campaign's settings. Changes to a live campaign are pushed to the platform, where some fields cannot be edited once running — the platform decides, and rejects with a `400` if not.\n\n#### Signature\n\n```http\nPUT /crm/ads/campaigns/{campaignId} (campaignId: string, body) -> The updated campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Pause a running campaign before making structural changes — many platforms refuse edits to an active campaign.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/pause/{campaignId}`","tags":["CRM · Ads"],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}},"example":{"budget":7500}}}}},"delete":{"operationId":"AdsController_deleteCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/{campaignId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete an ad campaign","description":"Deletes a campaign. Pause it first if it is running — deleting does not guarantee the platform stops serving immediately.\n\n#### Signature\n\n```http\nDELETE /crm/ads/campaigns/{campaignId} (campaignId: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Historical spend and metrics may be lost with the campaign — export analytics first if you need them.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/pause/{campaignId}`","tags":["CRM · Ads"]}},"/crm/ads/campaigns/launch/{campaignId}":{"post":{"operationId":"AdsController_launchCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"201":{"description":"The launched campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/launch/{campaignId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Launch a campaign","description":"Pushes a campaign to its platforms and starts it running.\n\n**This starts spending money.** From here the budget is consumed by the ad platforms, and stopping it requires an explicit pause.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/launch/{campaignId} (campaignId: string) -> The launched campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Spend begins immediately. A campaign that is rejected on one platform may still run on the others — check the result per platform.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/pause/{campaignId}`\n- `POST /crm/ads/campaigns/schedule/{campaignId}`","tags":["CRM · Ads"]}},"/crm/ads/campaigns/pause/{campaignId}":{"post":{"operationId":"AdsController_pauseCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"201":{"description":"The paused campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/pause/{campaignId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pause a campaign","description":"Stops a running campaign from serving and spending. The campaign and its history are kept, and it can be resumed.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/pause/{campaignId} (campaignId: string) -> The paused campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Platforms may take a few minutes to stop serving after a pause is accepted.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/resume/{campaignId}`","tags":["CRM · Ads"]}},"/crm/ads/campaigns/resume/{campaignId}":{"post":{"operationId":"AdsController_resumeCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"201":{"description":"The resumed campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/resume/{campaignId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Resume a campaign","description":"Restarts a paused campaign. Spending resumes against the remaining budget.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/resume/{campaignId} (campaignId: string) -> The resumed campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/pause/{campaignId}`","tags":["CRM · Ads"]}},"/crm/ads/campaigns/schedule/{campaignId}":{"post":{"operationId":"AdsController_scheduleCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"201":{"description":"The scheduled campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/schedule/{campaignId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Schedule a campaign","description":"Sets a campaign to start automatically at a future date rather than launching it now. Give an `endDate` to have it stop on its own.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/schedule/{campaignId} (campaignId: string, body) -> The scheduled campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Without an `endDate` the campaign runs until it is paused or the budget runs out.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/scheduled`","tags":["CRM · Ads"],"requestBody":{"description":"When the campaign should run.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["startDate"],"properties":{"startDate":{"type":"string","format":"date-time","description":"When the campaign starts.","example":"2026-09-01T00:00:00.000Z"},"endDate":{"type":"string","format":"date-time","description":"When it stops. Omit to run until paused or the budget is exhausted.","example":"2026-09-30T23:59:59.000Z"}}},"example":{"startDate":"2026-09-01T00:00:00.000Z","endDate":"2026-09-30T23:59:59.000Z"}}}}}},"/crm/ads/campaigns/duplicate/{campaignId}":{"post":{"operationId":"AdsController_duplicateCampaign","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"}],"responses":{"201":{"description":"The new draft campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/duplicate/{campaignId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Duplicate a campaign","description":"Copies a campaign into a new draft, optionally onto different platforms, with a different name or budget. The fast path for running a proven campaign again or extending it to another channel.\n\nThe duplicate is a **draft** — it does not inherit the original's running state.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/duplicate/{campaignId} (campaignId: string, body) -> The new draft campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- The copy starts as a draft and must be launched separately.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/from-template/{templateId}`","tags":["CRM · Ads"],"requestBody":{"description":"What to change in the copy.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Platforms for the copy. Defaults to the original's.","example":["tiktok"]},"name":{"type":"string","description":"Name for the copy.","example":"Summer sale — retargeting (TikTok)"},"budget":{"type":"number","example":2500},"modifications":{"type":"object","additionalProperties":true,"description":"Other fields to override."}}},"examples":{"sameSetup":{"summary":"Straight copy","value":{}},"newPlatform":{"summary":"Extend to another platform","value":{"platforms":["tiktok"],"name":"Summer sale — retargeting (TikTok)","budget":2500}}}}}}}},"/crm/ads/campaigns/bulk/launch":{"post":{"operationId":"AdsController_bulkLaunchCampaigns","parameters":[],"responses":{"201":{"description":"Per-campaign launch results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Launch several campaigns","description":"Launches many campaigns in one call.\n\n**This starts spending on every campaign named.** Individual failures are reported per campaign rather than failing the batch, so check the response — a `200` means the batch ran, not that everything launched.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/bulk/launch (body) -> Per-campaign launch results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Starts real spend across every campaign in the list. Inspect each result.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/bulk/pause`","tags":["CRM · Ads"],"requestBody":{"description":"Which campaigns to launch.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["campaignIds"],"properties":{"campaignIds":{"type":"array","items":{"type":"string"},"example":["CAMP-4821","CAMP-4822"]}}},"example":{"campaignIds":["CAMP-4821","CAMP-4822"]}}}}}},"/crm/ads/campaigns/bulk/pause":{"post":{"operationId":"AdsController_bulkPauseCampaigns","parameters":[],"responses":{"201":{"description":"Per-campaign pause results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pause several campaigns","description":"Pauses many campaigns at once — the emergency stop when spend needs to halt across the board. Results are reported per campaign.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/bulk/pause (body) -> Per-campaign pause results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Check every result — a campaign that failed to pause is still spending.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/bulk/launch`","tags":["CRM · Ads"],"requestBody":{"description":"Which campaigns to pause.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["campaignIds"],"properties":{"campaignIds":{"type":"array","items":{"type":"string"},"example":["CAMP-4821","CAMP-4822"]}}},"example":{"campaignIds":["CAMP-4821","CAMP-4822"]}}}}}},"/crm/ads/campaigns/metrics/{campaignId}":{"get":{"operationId":"AdsController_getCampaignMetrics","parameters":[{"name":"campaignId","required":true,"in":"path","schema":{"type":"string"},"description":"Campaign id.","example":"CAMP-4821"},{"name":"startDate","in":"query","required":false,"description":"Start of the reporting period.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","in":"query","required":false,"description":"End of the reporting period.","schema":{"type":"string"},"example":"2026-08-31"},{"name":"platforms","in":"query","required":false,"description":"Restrict to specific platforms. Repeat the parameter for several.","schema":{"type":"string"},"example":"facebook"},{"name":"groupBy","in":"query","required":false,"description":"How results are aggregated.","schema":{"type":"string","enum":["platform","date","campaign"]},"example":"platform"},{"name":"metrics","in":"query","required":false,"description":"Which metrics to return. Repeat for several.","schema":{"type":"string"},"example":"impressions"}],"responses":{"200":{"description":"Campaign metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Campaign not found — No campaign in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Campaign not found","path":"/crm/ads/campaigns/metrics/{campaignId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get campaign metrics","description":"Live performance figures for one campaign, pulled from its platforms — impressions, clicks, spend and conversions.\n\n#### Signature\n\n```http\nGET /crm/ads/campaigns/metrics/{campaignId} (campaignId: string, startDate?: string, endDate?: string, platforms?: string, groupBy?: string, metrics?: string) -> Campaign metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Figures come from the ad platforms and lag real time — most report on their own delay.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CAMPAIGN_NOT_FOUND | Campaign not found | No campaign in the org has that id. | Check the id with `GET /crm/ads/campaigns`. **Note this is a `400`, not a `404`** — this controller never returns 404. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/analytics/overview`","tags":["CRM · Ads"]}},"/crm/ads/analytics/overview":{"get":{"operationId":"AdsController_getAnalyticsOverview","parameters":[{"name":"startDate","in":"query","required":false,"description":"Start of the reporting period.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","in":"query","required":false,"description":"End of the reporting period.","schema":{"type":"string"},"example":"2026-08-31"},{"name":"platforms","in":"query","required":false,"description":"Restrict to specific platforms. Repeat the parameter for several.","schema":{"type":"string"},"example":"facebook"},{"name":"groupBy","in":"query","required":false,"description":"How results are aggregated.","schema":{"type":"string","enum":["platform","date","campaign"]},"example":"platform"},{"name":"metrics","in":"query","required":false,"description":"Which metrics to return. Repeat for several.","schema":{"type":"string"},"example":"impressions"}],"responses":{"200":{"description":"Overview metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get an advertising overview","description":"Headline advertising figures across every campaign and platform for a period — total spend, reach and conversions.\n\n#### Signature\n\n```http\nGET /crm/ads/analytics/overview (startDate?: string, endDate?: string, platforms?: string, groupBy?: string, metrics?: string) -> Overview metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/analytics/performance`","tags":["CRM · Ads"]}},"/crm/ads/analytics/performance":{"get":{"operationId":"AdsController_getPerformanceAnalytics","parameters":[{"name":"startDate","in":"query","required":false,"description":"Start of the reporting period.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","in":"query","required":false,"description":"End of the reporting period.","schema":{"type":"string"},"example":"2026-08-31"},{"name":"platforms","in":"query","required":false,"description":"Restrict to specific platforms. Repeat the parameter for several.","schema":{"type":"string"},"example":"facebook"},{"name":"groupBy","in":"query","required":false,"description":"How results are aggregated.","schema":{"type":"string","enum":["platform","date","campaign"]},"example":"platform"},{"name":"metrics","in":"query","required":false,"description":"Which metrics to return. Repeat for several.","schema":{"type":"string"},"example":"impressions"}],"responses":{"200":{"description":"Performance analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get performance analytics","description":"Detailed performance breakdown — cost per result, conversion rates and efficiency by whatever `groupBy` selects.\n\n#### Signature\n\n```http\nGET /crm/ads/analytics/performance (startDate?: string, endDate?: string, platforms?: string, groupBy?: string, metrics?: string) -> Performance analytics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/analytics/comparison`","tags":["CRM · Ads"]}},"/crm/ads/analytics/comparison":{"get":{"operationId":"AdsController_getPlatformComparison","parameters":[{"name":"startDate","in":"query","required":false,"description":"Start of the reporting period.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","in":"query","required":false,"description":"End of the reporting period.","schema":{"type":"string"},"example":"2026-08-31"},{"name":"platforms","in":"query","required":false,"description":"Restrict to specific platforms. Repeat the parameter for several.","schema":{"type":"string"},"example":"facebook"},{"name":"groupBy","in":"query","required":false,"description":"How results are aggregated.","schema":{"type":"string","enum":["platform","date","campaign"]},"example":"platform"},{"name":"metrics","in":"query","required":false,"description":"Which metrics to return. Repeat for several.","schema":{"type":"string"},"example":"impressions"}],"responses":{"200":{"description":"Platform comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Compare platforms","description":"Puts platforms side by side over the same period — the read behind \"is TikTok outperforming Facebook for us\".\n\n#### Signature\n\n```http\nGET /crm/ads/analytics/comparison (startDate?: string, endDate?: string, platforms?: string, groupBy?: string, metrics?: string) -> Platform comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Platforms define metrics like a conversion differently, so treat cross-platform comparisons as directional.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/analytics/trends`","tags":["CRM · Ads"]}},"/crm/ads/analytics/trends":{"get":{"operationId":"AdsController_getTrends","parameters":[{"name":"startDate","in":"query","required":false,"description":"Start of the reporting period.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","in":"query","required":false,"description":"End of the reporting period.","schema":{"type":"string"},"example":"2026-08-31"},{"name":"platforms","in":"query","required":false,"description":"Restrict to specific platforms. Repeat the parameter for several.","schema":{"type":"string"},"example":"facebook"},{"name":"groupBy","in":"query","required":false,"description":"How results are aggregated.","schema":{"type":"string","enum":["platform","date","campaign"]},"example":"platform"},{"name":"metrics","in":"query","required":false,"description":"Which metrics to return. Repeat for several.","schema":{"type":"string"},"example":"impressions"}],"responses":{"200":{"description":"Trend data","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get advertising trends","description":"Performance over time, for spotting drift in cost or effectiveness before it shows up in a monthly total.\n\n#### Signature\n\n```http\nGET /crm/ads/analytics/trends (startDate?: string, endDate?: string, platforms?: string, groupBy?: string, metrics?: string) -> Trend data\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/analytics/overview`","tags":["CRM · Ads"]}},"/crm/ads/campaigns/cross-platform":{"post":{"operationId":"AdsController_createCrossPlatformCampaign","parameters":[],"responses":{"201":{"description":"The created cross-platform campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"The ad platform rejected the request. — The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The ad platform rejected the request.","path":"/crm/ads/campaigns/cross-platform","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a cross-platform campaign","description":"Creates one campaign that runs across several platforms with a split budget and platform-specific settings.\n\n`platformSpecific` holds the per-platform overrides, keyed by platform name; `distribution.budgetSplit` divides the budget between them, and `distribution.priority` orders which platforms are set up first.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/cross-platform (body) -> The created cross-platform campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- `budgetSplit` is not validated against the total budget — the two can disagree.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLATFORM_ERROR | The ad platform rejected the request. | The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec. | The platform's own message is passed through. Check the integration is still connected with `GET /crm/ads/integrations/status`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns`","tags":["CRM · Ads"],"requestBody":{"description":"The cross-platform campaign definition.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"platformSpecific":{"type":"object","additionalProperties":true,"description":"Per-platform settings, keyed by platform name."},"distribution":{"type":"object","properties":{"budgetSplit":{"type":"object","additionalProperties":{"type":"number"},"description":"Budget share per platform, keyed by platform name."},"priority":{"type":"array","items":{"type":"string"},"description":"Order in which platforms are set up.","example":["facebook","tiktok"]}}}}},"example":{"name":"Summer sale — cross platform","budget":10000,"platformSpecific":{"facebook":{"objective":"conversions"},"tiktok":{"objective":"traffic"}},"distribution":{"budgetSplit":{"facebook":6000,"tiktok":4000},"priority":["facebook","tiktok"]}}}}}}},"/crm/ads/templates":{"post":{"operationId":"AdsController_createCampaignTemplate","parameters":[],"responses":{"201":{"description":"The created template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a campaign template","description":"Saves a reusable campaign setup, so a recurring campaign shape does not have to be rebuilt each time.\n\n#### Signature\n\n```http\nPOST /crm/ads/templates (body) -> The created template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/from-template/{templateId}`","tags":["CRM · Ads"],"requestBody":{"description":"The template to save.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","defaultSettings"],"properties":{"name":{"type":"string","example":"Seasonal retargeting"},"description":{"type":"string","example":"Standard retargeting setup for seasonal sales"},"defaultSettings":{"type":"object","additionalProperties":true,"description":"Settings applied to campaigns created from this template."},"category":{"type":"string","description":"Grouping, used by the list filter.","example":"retargeting"}}},"example":{"name":"Seasonal retargeting","description":"Standard retargeting setup","category":"retargeting","defaultSettings":{"objective":"conversions","platforms":["facebook","instagram"]}}}}}},"get":{"operationId":"AdsController_getCampaignTemplates","parameters":[{"name":"category","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by category.","example":"retargeting"}],"responses":{"200":{"description":"Campaign templates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List campaign templates","description":"The saved campaign templates, optionally filtered by category.\n\n#### Signature\n\n```http\nGET /crm/ads/templates (category?: string) -> Campaign templates\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/templates`","tags":["CRM · Ads"]}},"/crm/ads/campaigns/from-template/{templateId}":{"post":{"operationId":"AdsController_createCampaignFromTemplate","parameters":[{"name":"templateId","required":true,"in":"path","schema":{"type":"string"},"description":"Template id.","example":"TPL-4821"}],"responses":{"201":{"description":"The created draft campaign","content":{"application/json":{"schema":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a campaign from a template","description":"Builds a new draft campaign from a saved template. Like duplication, the result is a draft and must be launched separately.\n\n#### Signature\n\n```http\nPOST /crm/ads/campaigns/from-template/{templateId} (templateId: string, body) -> The created draft campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/templates`","tags":["CRM · Ads"],"requestBody":{"description":"Overrides for the template defaults.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Autumn sale — retargeting","budget":3000}}}}}},"/crm/ads/accounts":{"get":{"operationId":"AdsController_getAdAccounts","parameters":[{"name":"platform","required":false,"in":"query","schema":{"type":"string"},"description":"Ad platform key, e.g. `facebook`."}],"responses":{"200":{"description":"Connected ad accounts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List connected ad accounts","description":"The ad accounts connected to this org across platforms — what campaigns can actually be published to.\n\n#### Signature\n\n```http\nGET /crm/ads/accounts (platform?: string) -> Connected ad accounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/accounts/sync`","tags":["CRM · Ads"]}},"/crm/ads/accounts/sync":{"post":{"operationId":"AdsController_syncAdAccounts","parameters":[],"responses":{"201":{"description":"The sync result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The ad platform rejected the request. — The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The ad platform rejected the request.","path":"/crm/ads/accounts/sync","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Sync ad accounts","description":"Refreshes the connected account list from the platforms, picking up accounts added or removed on their side since the last sync.\n\n#### Signature\n\n```http\nPOST /crm/ads/accounts/sync () -> The sync result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLATFORM_ERROR | The ad platform rejected the request. | The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec. | The platform's own message is passed through. Check the integration is still connected with `GET /crm/ads/integrations/status`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/accounts`","tags":["CRM · Ads"]}},"/crm/ads/creatives":{"post":{"operationId":"AdsController_uploadCreative","parameters":[],"responses":{"201":{"description":"The registered creative","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The ad platform rejected the request. — The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The ad platform rejected the request.","path":"/crm/ads/creatives","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Upload a creative","description":"Registers an ad creative — an image, video or copy variant — for use across the named platforms. Supply either a `url` or an uploaded `file`.\n\n#### Signature\n\n```http\nPOST /crm/ads/creatives (body) -> The registered creative\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Platforms enforce their own size and aspect-ratio rules — a creative accepted here can still be rejected at launch.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLATFORM_ERROR | The ad platform rejected the request. | The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec. | The platform's own message is passed through. Check the integration is still connected with `GET /crm/ads/integrations/status`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/creatives`","tags":["CRM · Ads"],"requestBody":{"description":"The creative to register.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["platforms"],"properties":{"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Platforms this creative is for.","example":["facebook","instagram"]},"url":{"type":"string","description":"Where the asset lives. Use this or `file`.","example":"https://cdn.appmint.io/ads/summer-hero.jpg"},"file":{"type":"object","additionalProperties":true,"description":"Uploaded asset. Use this or `url`."},"metadata":{"type":"object","additionalProperties":true,"description":"Dimensions, format, alt text and the like."}}},"example":{"platforms":["facebook","instagram"],"url":"https://cdn.appmint.io/ads/summer-hero.jpg","metadata":{"width":1200,"height":628,"format":"jpg"}}}}}},"get":{"operationId":"AdsController_getCreatives","parameters":[{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by creative type.","example":"image"},{"name":"platform","required":false,"in":"query","schema":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"example":"facebook"}],"responses":{"200":{"description":"Creatives","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List creatives","description":"The registered ad creatives, optionally filtered by type or platform.\n\n#### Signature\n\n```http\nGET /crm/ads/creatives (type?: string, platform?: string) -> Creatives\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/creatives`","tags":["CRM · Ads"]}},"/crm/ads/audiences":{"post":{"operationId":"AdsController_createAudience","parameters":[],"responses":{"201":{"description":"The created audience","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The ad platform rejected the request. — The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The ad platform rejected the request.","path":"/crm/ads/audiences","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create an audience","description":"Defines a targeting audience and pushes it to the named platforms, so the same definition can be reused across campaigns instead of being rebuilt per campaign.\n\n#### Signature\n\n```http\nPOST /crm/ads/audiences (body) -> The created audience\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Platforms impose minimum audience sizes and may reject one that is too small to protect privacy.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLATFORM_ERROR | The ad platform rejected the request. | The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec. | The platform's own message is passed through. Check the integration is still connected with `GET /crm/ads/integrations/status`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/audiences`","tags":["CRM · Ads"],"requestBody":{"description":"The audience to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","platforms","targetingCriteria"],"properties":{"name":{"type":"string","example":"Cart abandoners — 30 days"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"example":["facebook","instagram"]},"targetingCriteria":{"type":"object","additionalProperties":true,"description":"Who is in the audience."}}},"example":{"name":"Cart abandoners — 30 days","platforms":["facebook","instagram"],"targetingCriteria":{"event":"cart_abandoned","withinDays":30}}}}}},"get":{"operationId":"AdsController_getAudiences","parameters":[{"name":"platform","required":false,"in":"query","schema":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"example":"facebook"}],"responses":{"200":{"description":"Audiences","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List audiences","description":"The defined targeting audiences, optionally filtered by platform.\n\n#### Signature\n\n```http\nGET /crm/ads/audiences (platform?: string) -> Audiences\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/audiences`","tags":["CRM · Ads"]}},"/crm/ads/automation/rules":{"post":{"operationId":"AdsController_createAutomationRule","parameters":[],"responses":{"201":{"description":"The created rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create an automation rule","description":"Defines a rule that acts on campaigns automatically — pausing one whose cost per result climbs too high, or shifting budget toward a performer.\n\nA rule is a `trigger` (what to watch), `conditions` (when it applies) and `actions` (what to do). Because the actions change live campaigns and therefore spend, test a rule on one campaign before applying it broadly.\n\n#### Signature\n\n```http\nPOST /crm/ads/automation/rules (body) -> The created rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Rules act on live campaigns without confirmation. A badly-scoped rule can pause everything you are running.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/automation/rules`","tags":["CRM · Ads"],"requestBody":{"description":"The rule to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","trigger","conditions","actions","platforms"],"properties":{"name":{"type":"string","example":"Pause on high CPA"},"trigger":{"type":"object","additionalProperties":true,"description":"What the rule watches."},"conditions":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"When it fires."},"actions":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"What it does. These change live campaigns."},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"example":["facebook"]}}},"example":{"name":"Pause on high CPA","platforms":["facebook"],"trigger":{"metric":"cpa","check":"hourly"},"conditions":[{"metric":"cpa","operator":">","value":50}],"actions":[{"type":"pause_campaign"}]}}}}},"get":{"operationId":"AdsController_getAutomationRules","parameters":[],"responses":{"200":{"description":"Automation rules","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List automation rules","description":"The automation rules configured for this org — worth checking first when a campaign changed state without anyone doing it.\n\n#### Signature\n\n```http\nGET /crm/ads/automation/rules () -> Automation rules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/automation/rules`","tags":["CRM · Ads"]}},"/crm/ads/reports/generate":{"post":{"operationId":"AdsController_generateReport","parameters":[],"responses":{"201":{"description":"The generated report, or a handle to it","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate an advertising report","description":"Builds a report over a period and set of campaigns. Reports are retrieved afterwards from `GET /crm/ads/reports`.\n\n#### Signature\n\n```http\nPOST /crm/ads/reports/generate (body) -> The generated report, or a handle to it\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Generation may be asynchronous — check the reports list rather than assuming the response is the finished report.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/reports`","tags":["CRM · Ads"],"requestBody":{"description":"What the report should cover.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"startDate":"2026-08-01","endDate":"2026-08-31","platforms":["facebook","tiktok"],"groupBy":"platform"}}}}}},"/crm/ads/reports":{"get":{"operationId":"AdsController_getReports","parameters":[],"responses":{"200":{"description":"Reports","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List advertising reports","description":"The reports generated for this org.\n\n#### Signature\n\n```http\nGET /crm/ads/reports () -> Reports\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/reports/generate`","tags":["CRM · Ads"]}},"/crm/ads/activity":{"get":{"operationId":"AdsController_getAdsActivity","parameters":[{"name":"platform","required":false,"in":"query","schema":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"example":"facebook"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by activity type.","example":"pause"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","default":50},"example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer","default":0},"description":"Offset paging — this endpoint uses `offset`, not `page`.","example":0}],"responses":{"200":{"description":"Activity entries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get advertising activity","description":"The activity log across advertising — launches, pauses, edits and automation-rule firings. The audit trail for \"why did this campaign stop\".\n\n#### Signature\n\n```http\nGET /crm/ads/activity (platform?: string, type?: string, limit?: integer, offset?: integer) -> Activity entries\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Paging is offset-based here, unlike the campaign list which pages by number.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/automation/rules`","tags":["CRM · Ads"]}},"/crm/ads/integrations/status":{"get":{"operationId":"AdsController_getIntegrationsStatus","parameters":[],"responses":{"200":{"description":"Integration status per platform","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"platform":{"type":"string","example":"facebook"},"connected":{"type":"boolean","example":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get platform integration status","description":"Whether each ad platform integration is connected and healthy. Check this first when campaigns fail to launch — an expired token presents as a platform error at launch time.\n\n#### Signature\n\n```http\nGET /crm/ads/integrations/status () -> Integration status per platform\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/integrations/test/{platform}`","tags":["CRM · Ads"]}},"/crm/ads/integrations/test/{platform}":{"post":{"operationId":"AdsController_testIntegration","parameters":[{"name":"platform","required":true,"in":"path","schema":{"type":"string"},"description":"Platform to test — one of `facebook`, `instagram`, `google`, `tiktok`, `linkedin`, `twitter`.","example":"facebook"}],"responses":{"201":{"description":"The test result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The ad platform rejected the request. — The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The ad platform rejected the request.","path":"/crm/ads/integrations/test/{platform}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Test a platform integration","description":"Makes a live call to one platform to confirm the credentials still work. Nothing is published — this only verifies the connection.\n\n#### Signature\n\n```http\nPOST /crm/ads/integrations/test/{platform} (platform: string) -> The test result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n- Read-only against the platform — safe to run at any time.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLATFORM_ERROR | The ad platform rejected the request. | The upstream ad platform returned an error — a policy rejection, an expired token, or an invalid targeting spec. | The platform's own message is passed through. Check the integration is still connected with `GET /crm/ads/integrations/status`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ads/integrations/status`","tags":["CRM · Ads"]}},"/crm/ads/scheduled":{"get":{"operationId":"AdsController_getScheduledCampaigns","parameters":[],"responses":{"200":{"description":"Scheduled campaigns","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An ad campaign (`crm_ads_campaign`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"campaignId":{"type":"string","example":"CAMP-4821"},"name":{"type":"string","example":"Summer sale — retargeting"},"status":{"type":"string","enum":["draft","scheduled","active","paused","completed","failed"],"example":"active"},"platforms":{"type":"array","items":{"type":"string","enum":["facebook","instagram","google","tiktok","linkedin","twitter"]},"description":"Where the campaign runs.","example":["facebook","instagram"]},"budget":{"type":"number","example":5000},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"objective":{"type":"string","description":"What the campaign optimises for.","example":"conversions"},"targeting":{"type":"object","additionalProperties":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List scheduled campaigns","description":"Campaigns waiting to start automatically — what is about to begin spending, and when.\n\n#### Signature\n\n```http\nGET /crm/ads/scheduled () -> Scheduled campaigns\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This controller takes **no `orgid` header**. The organization comes from `user.orgId` on the authenticated token, so a token scoped to the wrong org silently operates on the wrong data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ads/campaigns/schedule/{campaignId}`","tags":["CRM · Ads"]}},"/crm/promotions/{name}/subscribe":{"post":{"operationId":"PromotionController_subscribe","summary":"Subscribe to a promotion","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Promotion name.","schema":{"type":"string"},"example":"summer-newsletter"}],"responses":{"201":{"description":"The subscription","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Subscribes someone to a promotion or mailing list. Either `email` or `phone` identifies them, and `channel` decides how they are contacted.\n\n**`consentText` records what the person actually agreed to.** Store the exact wording shown at the point of signup — it is the evidence of consent, and a generic value undermines it. The UTM fields capture where the signup came from.\n\n#### Signature\n\n```http\nPOST /crm/promotions/{name}/subscribe (name: string, body) -> The subscription\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Depending on the promotion, a confirmation email may follow — see `GET /crm/promotions/confirm/{token}`.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /crm/promotions/confirm/{token}`\n- `POST /crm/promotions/{name}/unsubscribe`","requestBody":{"description":"Who is subscribing, and what they consented to.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Required unless `phone` is given.","example":"ada@example.com"},"phone":{"type":"string","description":"Required unless `email` is given.","example":"+15551234567"},"name":{"type":"string","example":"Ada Lovelace"},"channel":{"type":"string","enum":["email","sms"],"description":"How they will be contacted.","example":"email"},"source":{"type":"string","description":"Where the signup happened.","example":"footer-form"},"sourceUrl":{"type":"string","example":"https://shop.example.com/"},"utmSource":{"type":"string","example":"newsletter"},"utmMedium":{"type":"string","example":"email"},"utmCampaign":{"type":"string","example":"summer-2026"},"consentText":{"type":"string","description":"The exact wording the person agreed to. Store it verbatim.","example":"I agree to receive marketing emails from Acme Retail. Unsubscribe any time."}}},"example":{"email":"ada@example.com","name":"Ada Lovelace","channel":"email","source":"footer-form","consentText":"I agree to receive marketing emails from Acme Retail. Unsubscribe any time."}}}}}},"/crm/promotions/confirm/{token}":{"get":{"operationId":"PromotionController_confirmSubscription","summary":"Confirm a subscription","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"token","required":true,"in":"path","description":"Confirmation token from the email.","schema":{"type":"string"},"example":"cnf_9k2m4h1p7q"}],"responses":{"200":{"description":"The confirmation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Confirms a double opt-in subscription from the link in a confirmation email. The token is single-purpose and identifies the subscriber, so treat the link as a credential.\n\n#### Signature\n\n```http\nGET /crm/promotions/confirm/{token} (token: string) -> The confirmation result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public by necessity — the recipient clicks it from their inbox without signing in.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/promotions/{name}/subscribe`"}},"/crm/promotions/{name}/unsubscribe":{"post":{"operationId":"PromotionController_unsubscribe","summary":"Unsubscribe from a promotion","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Promotion name.","schema":{"type":"string"},"example":"summer-newsletter"}],"responses":{"201":{"description":"The unsubscribe result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Removes someone from a promotion. Honour this promptly and completely — an unsubscribe that keeps sending is a compliance problem, not just a bug.\n\n#### Signature\n\n```http\nPOST /crm/promotions/{name}/unsubscribe (name: string, body) -> The unsubscribe result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /crm/promotions/preferences`","requestBody":{"description":"Who is unsubscribing.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string"}}},"example":{"email":"ada@example.com"}}}}}},"/crm/promotions/preferences":{"get":{"operationId":"PromotionController_getPreferences","summary":"Get subscription preferences","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":false,"in":"query","schema":{"type":"string"},"description":"Subscriber email."},{"name":"phone","required":false,"in":"query","schema":{"type":"string"},"description":"Subscriber phone."}],"responses":{"200":{"description":"Subscription preferences","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"A subscriber's current preferences across promotions — what a preference centre reads to show which lists someone is on.\n\n#### Signature\n\n```http\nGET /crm/promotions/preferences (email?: string, phone?: string) -> Subscription preferences\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/promotions/{name}/unsubscribe`"}},"/crm/promotions":{"post":{"operationId":"PromotionController_createPromotion","summary":"Create a promotion","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created promotion","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Creates a promotion or mailing list that people can subscribe to.\n\n#### Signature\n\n```http\nPOST /crm/promotions (body) -> The created promotion\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/promotions`","requestBody":{"description":"The promotion to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"summer-newsletter"},"title":{"type":"string","example":"Summer Newsletter"},"incentive":{"type":"object","additionalProperties":true}}},"example":{"name":"summer-newsletter","title":"Summer Newsletter"}}}}},"get":{"operationId":"PromotionController_listPromotions","summary":"List promotions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by status."},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by type."}],"responses":{"200":{"description":"Promotions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"The promotions defined for the org.\n\n#### Signature\n\n```http\nGET /crm/promotions (status?: string, type?: string) -> Promotions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/promotions/{name}`"}},"/crm/promotions/{name}":{"get":{"operationId":"PromotionController_getPromotion","summary":"Get a promotion","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Promotion name.","example":"summer-newsletter"}],"responses":{"200":{"description":"The promotion","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Promotion not found — No promotion has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Promotion not found","path":"/crm/promotions/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Fetches one promotion by name.\n\n#### Signature\n\n```http\nGET /crm/promotions/{name} (name: string) -> The promotion\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Promotion not found | No promotion has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/promotions/{name}`"},"put":{"operationId":"PromotionController_updatePromotion","summary":"Update a promotion","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Promotion name.","example":"summer-newsletter"}],"responses":{"200":{"description":"The updated promotion","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Promotion not found — No promotion has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Promotion not found","path":"/crm/promotions/{name}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Updates a promotion's details. Changing the consent wording here does not alter what existing subscribers agreed to — their recorded `consentText` stands.\n\n#### Signature\n\n```http\nPUT /crm/promotions/{name} (name: string, body) -> The updated promotion\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Promotion not found | No promotion has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/promotions/{name}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Summer Newsletter 2026"}}}}},"delete":{"operationId":"PromotionController_deletePromotion","summary":"Delete a promotion","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Promotion name.","example":"summer-newsletter"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Promotion not found — No promotion has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Promotion not found","path":"/crm/promotions/{name}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Deletes a promotion. Its subscriber list goes with it, including the consent records — export them first if you need to prove consent later.\n\n#### Signature\n\n```http\nDELETE /crm/promotions/{name} (name: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Consent evidence is deleted along with the subscribers.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Promotion not found | No promotion has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/promotions/{name}/subscribers`"}},"/crm/promotions/{name}/subscribers":{"get":{"operationId":"PromotionController_listSubscribers","summary":"List promotion subscribers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Promotion name.","example":"summer-newsletter"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by status."},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."},{"name":"pageSize","required":false,"in":"query","schema":{"type":"string"},"description":"Rows per page."}],"responses":{"200":{"description":"Subscribers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Everyone subscribed to a promotion, with their consent record and where they signed up from.\n\n#### Signature\n\n```http\nGET /crm/promotions/{name}/subscribers (name: string, status?: string, page?: string, pageSize?: string) -> Subscribers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Contains personal data and consent evidence — restrict access accordingly.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/promotions/{name}/subscribe`"}},"/crm/promotions/{name}/incentive-claimed":{"post":{"operationId":"PromotionController_markIncentiveClaimed","summary":"Record an incentive claim","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Promotion name.","example":"summer-newsletter"}],"responses":{"201":{"description":"The claim result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Promotions"],"description":"Records that a subscriber claimed the promotion's incentive — the discount code or freebie offered for signing up — so it is not given twice.\n\n#### Signature\n\n```http\nPOST /crm/promotions/{name}/incentive-claimed (name: string, body) -> The claim result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/promotions/{name}/subscribers`","requestBody":{"description":"Who claimed it.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"ada@example.com"}}},"example":{"email":"ada@example.com"}}}}}},"/crm/merchant-customers/dashboard":{"get":{"operationId":"MerchantCustomerController_getDashboard","summary":"Get merchant dashboard metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Cross-merchant dashboard metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Headline figures across every merchant account — total credit extended, outstanding balance and overdue exposure.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/dashboard () -> Cross-merchant dashboard metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/balances/outstanding`"}},"/crm/merchant-customers/invoices/all":{"get":{"operationId":"MerchantCustomerController_getAllInvoices","summary":"Get all invoices across merchants","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","description":"Filter by invoice status.","schema":{"type":"string"},"example":"overdue"},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."},{"name":"pageSize","required":false,"in":"query","schema":{"type":"string"},"description":"Rows per page."}],"responses":{"200":{"description":"Invoices across all merchants","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A merchant invoice.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"invoiceNumber":{"type":"string","example":"MINV-00412"},"amount":{"type":"number","example":4200},"status":{"type":"string","example":"sent"},"dueDate":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Every merchant invoice in the org, for a consolidated receivables view.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/invoices/all (page?: string, pageSize?: string, status?: string) -> Invoices across all merchants\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/reports/aging`"}},"/crm/merchant-customers/transactions/all":{"get":{"operationId":"MerchantCustomerController_getAllTransactions","summary":"Get all transactions across merchants","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"type","required":false,"in":"query","description":"Filter by type.","schema":{"type":"string"}},{"name":"startDate","required":false,"in":"query","description":"Start of the period.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"End of the period.","schema":{"type":"string"},"example":"2026-08-31"},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."},{"name":"pageSize","required":false,"in":"query","schema":{"type":"string"},"description":"Rows per page."}],"responses":{"200":{"description":"Transactions across all merchants","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Every charge and credit across all merchant accounts — the consolidated ledger.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/transactions/all (type?: string, page?: string, pageSize?: string, startDate?: string, endDate?: string) -> Transactions across all merchants\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/transactions/{id}`"}},"/crm/merchant-customers/balances/outstanding":{"get":{"operationId":"MerchantCustomerController_getOutstandingBalances","summary":"Get outstanding balances","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Outstanding balance per merchant","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"What every merchant currently owes — the \"who owes what\" list, ordered so the largest exposures are visible first.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/balances/outstanding () -> Outstanding balance per merchant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/reports/aging`"}},"/crm/merchant-customers/reports/aging":{"get":{"operationId":"MerchantCustomerController_getAgingReport","summary":"Get the accounts receivable aging report","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Receivables bucketed by age","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Buckets outstanding receivables by how overdue they are — the standard 30 / 60 / 90 day aging report finance uses to judge collectability.\n\nAges are measured against each invoice's due date, which comes from the account's `paymentTermsDays`.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/reports/aging () -> Receivables bucketed by age\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/reminders/bulk`"}},"/crm/merchant-customers/reminders/{merchantId}/{invoiceId}":{"post":{"operationId":"MerchantCustomerController_sendPaymentReminder","summary":"Send a payment reminder","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"merchantId","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"invoiceId","required":true,"in":"path","description":"Invoice id.","schema":{"type":"string"},"example":"MINV-00412"}],"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/reminders/{merchantId}/{invoiceId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Sends a reminder to one merchant about one overdue invoice.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/reminders/{merchantId}/{invoiceId} (merchantId: string, invoiceId: string) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends every time it is called — there is no once-per-day guard.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/reminders/bulk`"}},"/crm/merchant-customers/reminders/bulk":{"post":{"operationId":"MerchantCustomerController_sendBulkPaymentReminders","summary":"Send reminders for all overdue invoices","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was sent, per merchant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Sends a payment reminder for **every** overdue invoice across every merchant.\n\nThis mails customers in bulk with no dry-run and no per-recipient throttle. Check the aging report first to see who it will reach.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/reminders/bulk () -> What was sent, per merchant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Org-wide mailing. Running it twice in a day chases every merchant twice.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/reports/aging`"}},"/crm/merchant-customers/statements/{merchantId}":{"get":{"operationId":"MerchantCustomerController_generateStatement","summary":"Generate an account statement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"merchantId","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"startDate","required":true,"in":"query","description":"Start of the period.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":true,"in":"query","description":"End of the period.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"The account statement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/statements/{merchantId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Builds a statement of account for a merchant over a period — the charges, credits and invoices that make up their balance. Both dates are required.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/statements/{merchantId} (merchantId: string, startDate?: string, endDate?: string) -> The account statement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/statements/{merchantId}/send`"}},"/crm/merchant-customers/statements/{merchantId}/send":{"post":{"operationId":"MerchantCustomerController_sendStatement","summary":"Generate and send an account statement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"merchantId","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/statements/{merchantId}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Builds the statement for a period and emails it to the merchant. Unlike the read, the period is supplied in the body.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/statements/{merchantId}/send (merchantId: string, body) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The read takes the period as query parameters; this one takes it in the body.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/statements/{merchantId}`","requestBody":{"description":"The period to cover.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["startDate","endDate"],"properties":{"startDate":{"type":"string","example":"2026-08-01"},"endDate":{"type":"string","example":"2026-08-31"}}},"example":{"startDate":"2026-08-01","endDate":"2026-08-31"}}}}}},"/crm/merchant-customers/invoices/generate-all":{"post":{"operationId":"MerchantCustomerController_generateBulkInvoices","summary":"Generate invoices for all merchants","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The invoices generated, per merchant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Raises invoices across **every** merchant with unbilled charges — the month-end billing run.\n\nThis creates real invoices for real money across the whole book. Check the response for what it produced before sending them.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/invoices/generate-all () -> The invoices generated, per merchant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Org-wide. Invoices are created as drafts — sending them is a separate step.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/invoices/send-all`"}},"/crm/merchant-customers/invoices/send-all":{"post":{"operationId":"MerchantCustomerController_sendAllDraftInvoices","summary":"Send all draft invoices","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was sent, per merchant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Sends every draft invoice across all merchants, starting the payment-terms clock on each. The companion to the bulk generation step — review the drafts before running it.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/invoices/send-all () -> What was sent, per merchant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Mails every merchant with a draft invoice. There is no dry run — inspect the drafts first.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/invoices/generate-all`"}},"/crm/merchant-customers/detail":{"post":{"operationId":"MerchantCustomerController_createMerchant","summary":"Create a merchant account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The account to open.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["customer","companyName"],"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}},"example":{"customer":"cus_4821","companyName":"Analytical Engines Ltd","accountType":"trade","spendingLimit":25000,"paymentTermsDays":30,"billingCycleDay":1,"autoInvoice":true,"invoiceTrigger":"period_end"}}}},"responses":{"201":{"description":"The created merchant account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Customer ID is required to create a merchant account — `customer` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Customer ID is required to create a merchant account","path":"/crm/merchant-customers/detail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Customer not found — The `customer` id does not resolve.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Customer not found","path":"/crm/merchant-customers/detail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This customer already has a merchant account — The customer already holds one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This customer already has a merchant account","path":"/crm/merchant-customers/detail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Opens a trade-credit account for an existing customer. The customer must already exist — this attaches credit terms to them rather than creating a new party.\n\n`customer` and `companyName` are both required, and a customer can only hold one merchant account.\n\nThis endpoint returns a coded error body — `{ message, code, ... }` — rather than the platform's standard envelope. Branch on `code`; it is stable, and the message is not.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/detail (body) -> The created merchant account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_CUSTOMER | Customer ID is required to create a merchant account | `customer` is missing. | Create the customer first, then open the account against them. |\n| `404` | CUSTOMER_NOT_FOUND | Customer not found | The `customer` id does not resolve. | The body echoes the `customerId` it tried. Check it exists. |\n| `409` | ACCOUNT_EXISTS | This customer already has a merchant account | The customer already holds one. | Look it up with `GET /crm/merchant-customers/by-customer/{customerId}` and update that instead. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/approve/{id}`\n- `GET /crm/merchant-customers/by-customer/{customerId}`"},"get":{"operationId":"MerchantCustomerController_getMerchants","summary":"List merchant accounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["pending","active","suspended","closed"]},"example":"active"},{"name":"accountType","required":false,"in":"query","schema":{"type":"string"},"example":"trade"},{"name":"search","required":false,"in":"query","schema":{"type":"string"},"description":"Free-text search across company name and contact.","example":"analytical"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of merchant accounts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}},"total":{"type":"integer","example":37}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Lists merchant accounts with filtering, search and paging.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/detail (status?: string, accountType?: string, search?: string, page?: integer, pageSize?: integer) -> A page of merchant accounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/detail/{id}`"}},"/crm/merchant-customers/detail/{id}":{"get":{"operationId":"MerchantCustomerController_getMerchant","summary":"Get a merchant account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"200":{"description":"The merchant account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/detail/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/detail/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Fetches one merchant account with its limits, balance, terms and authorized users.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/detail/{id} (id: string) -> The merchant account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/stats/{id}`"},"put":{"operationId":"MerchantCustomerController_updateMerchant","summary":"Update a merchant account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"companyName":"Analytical Engines Ltd (EMEA)"}}}},"responses":{"200":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/detail/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/detail/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Updates an account's general fields. Status changes, limits and payment terms each have their own endpoint, which record the change properly.\n\n#### Signature\n\n```http\nPUT /crm/merchant-customers/detail/{id} (id: string, body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/merchant-customers/spending-limit/{id}`"}},"/crm/merchant-customers/by-customer/{customerId}":{"get":{"operationId":"MerchantCustomerController_getMerchantByCustomer","summary":"Get a merchant account by customer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"customerId","required":true,"in":"path","description":"Customer id.","schema":{"type":"string"},"example":"cus_4821"}],"responses":{"200":{"description":"The customer's merchant account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/by-customer/{customerId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Finds the merchant account belonging to a customer. Use this when you hold the customer id and need to know whether they trade on credit.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/by-customer/{customerId} (customerId: string) -> The customer's merchant account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/detail`"}},"/crm/merchant-customers/approve/{id}":{"post":{"operationId":"MerchantCustomerController_approveMerchant","summary":"Approve a merchant account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The approved account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/approve/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/approve/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Activates a pending account so it can be charged against. This is the credit decision — nothing can be charged before it.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/approve/{id} (id: string, body) -> The approved account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/suspend/{id}`","requestBody":{"description":"Optional approval notes.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"notes":{"type":"string","example":"Credit check passed, limit set at 25k"}}},"example":{"notes":"Credit check passed"}}}}}},"/crm/merchant-customers/suspend/{id}":{"post":{"operationId":"MerchantCustomerController_suspendMerchant","summary":"Suspend a merchant account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The suspended account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/suspend/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/suspend/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Suspends an account so nothing further can be charged to it. The outstanding balance stands and remains collectable — suspension stops new credit, it does not forgive existing debt.\n\nA `reason` is required, and a closed account cannot be suspended.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/suspend/{id} (id: string, body) -> The suspended account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/reactivate/{id}`","requestBody":{"description":"Why the account is being suspended.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Recorded on the account.","example":"Invoices 60 days overdue"}}},"example":{"reason":"Invoices 60 days overdue"}}}}}},"/crm/merchant-customers/reactivate/{id}":{"post":{"operationId":"MerchantCustomerController_reactivateMerchant","summary":"Reactivate a merchant account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The reactivated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/reactivate/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/reactivate/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Lifts a suspension and returns the account to active, restoring its ability to be charged. The counterpart to suspend; a closed account cannot be reactivated.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/reactivate/{id} (id: string, body) -> The reactivated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/suspend/{id}`","requestBody":{"description":"Optional notes.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"notes":{"type":"string","example":"Arrears cleared"}}},"example":{"notes":"Arrears cleared"}}}}}},"/crm/merchant-customers/close/{id}":{"post":{"operationId":"MerchantCustomerController_closeMerchant","summary":"Close a merchant account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The closed account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/close/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/close/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Closes an account permanently. Unlike suspension there is no reopen — closure is terminal, so suspend instead where the relationship might resume.\n\nA `reason` is required.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/close/{id} (id: string, body) -> The closed account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Terminal. Closing does not settle an outstanding balance — collect it before or after, but closure does not write it off.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/suspend/{id}`","requestBody":{"description":"Why the account is being closed.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Customer ceased trading"}}},"example":{"reason":"Customer ceased trading"}}}}}},"/crm/merchant-customers/spending-limit/{id}":{"put":{"operationId":"MerchantCustomerController_setSpendingLimit","summary":"Set a spending limit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"200":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/spending-limit/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/spending-limit/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Sets the maximum credit the account may carry. `availableCredit` is this figure minus the current balance, and a charge that would exceed it is refused.\n\nLowering the limit below the current balance does not claw anything back; it simply leaves no available credit until the balance comes down.\n\n#### Signature\n\n```http\nPUT /crm/merchant-customers/spending-limit/{id} (id: string, body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/merchant-customers/payment-terms/{id}`","requestBody":{"description":"The new limit.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["limit"],"properties":{"limit":{"type":"number","description":"Maximum outstanding credit.","example":25000},"notes":{"type":"string","example":"Raised after two years of clean payment history"}}},"example":{"limit":25000,"notes":"Raised after two years of clean payment history"}}}}}},"/crm/merchant-customers/payment-terms/{id}":{"put":{"operationId":"MerchantCustomerController_setPaymentTerms","summary":"Set payment terms","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"200":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/payment-terms/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/payment-terms/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Sets how and when the account is invoiced.\n\n`invoiceTrigger` decides when an invoice is raised:\n\n- `period_end` — on the billing cycle day.\n- `limit_reached` — when the balance reaches the spending limit.\n- `either` — whichever comes first.\n- `manual` — never automatically; you raise invoices yourself.\n\n`paymentTermsDays` sets the due date relative to the invoice, and drives the aging report.\n\n#### Signature\n\n```http\nPUT /crm/merchant-customers/payment-terms/{id} (id: string, body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/invoices/generate/{id}`","requestBody":{"description":"The terms to apply.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","description":"Raise invoices automatically.","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"example":"period_end"},"notes":{"type":"string"}}},"examples":{"monthly":{"summary":"Net 30, invoiced monthly","value":{"paymentTermsDays":30,"billingCycleDay":1,"autoInvoice":true,"invoiceTrigger":"period_end"}},"onLimit":{"summary":"Invoice when the limit is reached","value":{"paymentTermsDays":14,"autoInvoice":true,"invoiceTrigger":"limit_reached"}}}}}}}},"/crm/merchant-customers/users/{id}":{"post":{"operationId":"MerchantCustomerController_addUser","summary":"Add an authorized user","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/users/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/users/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Grants someone the right to charge to the account. Give them their own `spendingLimit` to cap what they can commit — a purchasing clerk and a finance director on the same account need different ceilings.\n\n`enabledServices` restricts what they may charge for.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/users/{id} (id: string, body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/merchant-customers/users/{id}/{customerId}`","requestBody":{"description":"Who to authorize, and with what powers.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["customer"],"properties":{"customer":{"type":"string","description":"Customer id of the person being authorized.","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","default":false,"description":"Whether they may approve others' charges.","example":false},"spendingLimit":{"type":"number","description":"Their own cap, within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What they may charge for. Omit for everything.","example":["catering"]}}},"example":{"customer":"cus_9912","canApproveTransactions":false,"spendingLimit":5000,"enabledServices":["catering"]}}}}}},"/crm/merchant-customers/users/{id}/{customerId}":{"put":{"operationId":"MerchantCustomerController_updateUser","summary":"Update an authorized user","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"customerId","required":true,"in":"path","description":"Customer id of the authorized user.","schema":{"type":"string"},"example":"cus_9912"}],"responses":{"200":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/users/{id}/{customerId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/users/{id}/{customerId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Changes an authorized user's limit, approval right or permitted services.\n\n#### Signature\n\n```http\nPUT /crm/merchant-customers/users/{id}/{customerId} (id: string, customerId: string, body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /crm/merchant-customers/users/{id}/{customerId}`","requestBody":{"description":"What to change.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"canApproveTransactions":{"type":"boolean","example":true},"spendingLimit":{"type":"number","example":10000},"enabledServices":{"type":"array","items":{"type":"string"}}}},"example":{"spendingLimit":10000,"canApproveTransactions":true}}}}},"delete":{"operationId":"MerchantCustomerController_removeUser","summary":"Remove an authorized user","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"customerId","required":true,"in":"path","description":"Customer id of the authorized user.","schema":{"type":"string"},"example":"cus_9912"}],"responses":{"200":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/users/{id}/{customerId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/users/{id}/{customerId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Revokes someone's right to charge to the account. Charges they already made stand — this stops future ones only.\n\n#### Signature\n\n```http\nDELETE /crm/merchant-customers/users/{id}/{customerId} (id: string, customerId: string) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/authorized/{id}/{customerId}`"}},"/crm/merchant-customers/charge/{id}":{"post":{"operationId":"MerchantCustomerController_chargeToAccount","summary":"Charge to an account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/charge/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/charge/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Adds a charge to the account, increasing the balance and reducing available credit. This is how a purchase goes \"on account\" instead of being paid at the till.\n\n`id`, `type` and `amount` are all required, and the amount must be positive. Depending on the account's `invoiceTrigger`, a charge that reaches the spending limit can cause an invoice to be raised automatically.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/charge/{id} (id: string, body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent — nothing dedupes on the transaction `id`, so a retry charges twice.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/credit/{id}`\n- `GET /crm/merchant-customers/transactions/{id}`","requestBody":{"description":"The charge to record.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","amount"],"properties":{"id":{"type":"string","description":"Transaction id — your reference for this charge.","example":"TXN-99182"},"type":{"type":"string","description":"What kind of charge.","example":"order"},"amount":{"type":"number","description":"Must be greater than zero.","example":420},"reference":{"type":"string","description":"Related record, e.g. an order number.","example":"A7K2M9QX4"},"description":{"type":"string","example":"Catering order, 12 August"}}},"example":{"id":"TXN-99182","type":"order","amount":420,"reference":"A7K2M9QX4","description":"Catering order, 12 August"}}}}}},"/crm/merchant-customers/credit/{id}":{"post":{"operationId":"MerchantCustomerController_creditAccount","summary":"Credit an account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","description":"A merchant account (`crm_merchant_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"merchantId":{"type":"string","example":"MER-4821"},"customer":{"type":"string","description":"The customer record this account belongs to.","example":"cus_4821"},"companyName":{"type":"string","example":"Analytical Engines Ltd"},"accountType":{"type":"string","description":"Account classification.","example":"trade"},"status":{"type":"string","enum":["pending","active","suspended","closed"],"example":"active"},"spendingLimit":{"type":"number","description":"Maximum outstanding credit allowed.","example":25000},"currentBalance":{"type":"number","description":"Currently owed.","example":4200},"availableCredit":{"type":"number","description":"`spendingLimit` minus `currentBalance`.","example":20800},"paymentTermsDays":{"type":"number","description":"Days to pay after invoicing.","example":30},"billingCycleDay":{"type":"number","description":"Day of the month invoices are raised.","example":1},"autoInvoice":{"type":"boolean","example":true},"invoiceTrigger":{"type":"string","enum":["limit_reached","period_end","either","manual"],"description":"What causes an invoice to be raised.","example":"period_end"},"authorizedUsers":{"type":"array","description":"People allowed to charge to this account.","items":{"type":"object","properties":{"customer":{"type":"string","example":"cus_9912"},"canApproveTransactions":{"type":"boolean","example":false},"spendingLimit":{"type":"number","description":"Per-user cap within the account limit.","example":5000},"enabledServices":{"type":"array","items":{"type":"string"},"description":"What this user may charge for."}}}},"suspendedReason":{"type":"string"},"closedReason":{"type":"string"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/credit/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/credit/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Reduces the balance — a goodwill credit, a correction, or a returned order. A `reason` is required so the ledger explains itself.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/credit/{id} (id: string, body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A credit is not a payment — record customer payments against an invoice with the payment endpoint so the invoice closes.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/invoices/payment/{id}/{invoiceId}`","requestBody":{"description":"The credit to apply.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","reason"],"properties":{"amount":{"type":"number","example":120},"reason":{"type":"string","description":"Recorded on the ledger entry.","example":"Returned two trays, credited"}}},"example":{"amount":120,"reason":"Returned two trays, credited"}}}}}},"/crm/merchant-customers/invoices/generate/{id}":{"post":{"operationId":"MerchantCustomerController_generateInvoice","summary":"Generate an invoice for an account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"201":{"description":"The generated invoice","content":{"application/json":{"schema":{"type":"object","description":"A merchant invoice.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"invoiceNumber":{"type":"string","example":"MINV-00412"},"amount":{"type":"number","example":4200},"status":{"type":"string","example":"sent"},"dueDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/invoices/generate/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/invoices/generate/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Raises an invoice for the account's unbilled charges. Use this when the account is on `manual` invoicing, or to bill early.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/invoices/generate/{id} (id: string) -> The generated invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/invoices/send/{id}/{invoiceId}`"}},"/crm/merchant-customers/invoices/send/{id}/{invoiceId}":{"post":{"operationId":"MerchantCustomerController_sendInvoice","summary":"Send an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"invoiceId","required":true,"in":"path","description":"Invoice id.","schema":{"type":"string"},"example":"MINV-00412"}],"responses":{"201":{"description":"The sent invoice","content":{"application/json":{"schema":{"type":"object","description":"A merchant invoice.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"invoiceNumber":{"type":"string","example":"MINV-00412"},"amount":{"type":"number","example":4200},"status":{"type":"string","example":"sent"},"dueDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/invoices/send/{id}/{invoiceId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/invoices/send/{id}/{invoiceId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Delivers a generated invoice to the merchant, moving it out of draft and starting the payment-terms clock.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/invoices/send/{id}/{invoiceId} (id: string, invoiceId: string) -> The sent invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/invoices/payment/{id}/{invoiceId}`"}},"/crm/merchant-customers/invoices/payment/{id}/{invoiceId}":{"post":{"operationId":"MerchantCustomerController_recordPayment","summary":"Record an invoice payment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"invoiceId","required":true,"in":"path","description":"Invoice id.","schema":{"type":"string"},"example":"MINV-00412"}],"responses":{"201":{"description":"The updated invoice","content":{"application/json":{"schema":{"type":"object","description":"A merchant invoice.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"invoiceNumber":{"type":"string","example":"MINV-00412"},"amount":{"type":"number","example":4200},"status":{"type":"string","example":"sent"},"dueDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/invoices/payment/{id}/{invoiceId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/invoices/payment/{id}/{invoiceId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Records a payment against an invoice, reducing the account balance and settling the invoice when it is covered in full.\n\nAn already-paid invoice is refused rather than double-credited.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/invoices/payment/{id}/{invoiceId} (id: string, invoiceId: string, body) -> The updated invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId}`","requestBody":{"description":"The payment received.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Must be greater than zero.","example":4200},"method":{"type":"string","example":"bank_transfer"},"transactionId":{"type":"string","description":"Reference for reconciliation.","example":"BACS-99182"},"notes":{"type":"string"}}},"example":{"amount":4200,"method":"bank_transfer","transactionId":"BACS-99182"}}}}}},"/crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId}":{"post":{"operationId":"MerchantCustomerController_markOverdue","summary":"Mark an invoice overdue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"invoiceId","required":true,"in":"path","description":"Invoice id.","schema":{"type":"string"},"example":"MINV-00412"}],"responses":{"201":{"description":"The updated invoice","content":{"application/json":{"schema":{"type":"object","description":"A merchant invoice.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"invoiceNumber":{"type":"string","example":"MINV-00412"},"amount":{"type":"number","example":4200},"status":{"type":"string","example":"sent"},"dueDate":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Flags an invoice as overdue, bringing it into the aging report and the bulk reminder run.\n\n#### Signature\n\n```http\nPOST /crm/merchant-customers/invoices/mark-overdue/{id}/{invoiceId} (id: string, invoiceId: string) -> The updated invoice\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/reports/aging`"}},"/crm/merchant-customers/stats/{id}":{"get":{"operationId":"MerchantCustomerController_getStats","summary":"Get account statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"}],"responses":{"200":{"description":"Account statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/stats/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/stats/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Aggregate figures for one account — spend, payment history and outstanding balance.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/stats/{id} (id: string) -> Account statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/dashboard`"}},"/crm/merchant-customers/transactions/{id}":{"get":{"operationId":"MerchantCustomerController_getTransactions","summary":"Get an account's transaction history","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."},{"name":"pageSize","required":false,"in":"query","schema":{"type":"string"},"description":"Rows per page."}],"responses":{"200":{"description":"The account's transactions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/transactions/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/transactions/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"The charge and credit ledger for one account — what makes up the balance.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/transactions/{id} (id: string, page?: string, pageSize?: string) -> The account's transactions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/merchant-customers/transactions/all`"}},"/crm/merchant-customers/authorized/{id}/{customerId}":{"get":{"operationId":"MerchantCustomerController_checkAuthorized","summary":"Check whether a user is authorized","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Merchant account id.","schema":{"type":"string"},"example":"MER-4821"},{"name":"customerId","required":true,"in":"path","description":"Customer id of the authorized user.","schema":{"type":"string"},"example":"cus_9912"},{"name":"service","required":false,"in":"query","schema":{"type":"string"},"description":"Service key."}],"responses":{"200":{"description":"Authorization status and limits","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"authorized":{"type":"boolean","example":true},"spendingLimit":{"type":"number","example":5000}}}}}},"400":{"description":"Merchant ID is required — The merchant id path segment is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Merchant ID is required","path":"/crm/merchant-customers/authorized/{id}/{customerId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Merchant account not found — No merchant account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Merchant account not found","path":"/crm/merchant-customers/authorized/{id}/{customerId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Merchant accounts"],"description":"Reports whether a customer may charge to an account, and under what limits. Call this before letting someone put something on account.\n\n#### Signature\n\n```http\nGET /crm/merchant-customers/authorized/{id}/{customerId} (id: string, customerId: string, service?: string) -> Authorization status and limits\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | MISSING_MERCHANT_ID | Merchant ID is required | The merchant id path segment is empty. | Supply the account id. |\n| `404` | MERCHANT_NOT_FOUND | Merchant account not found | No merchant account has that id. | List accounts with `GET /crm/merchant-customers/detail`. The body carries `code: \"MERCHANT_NOT_FOUND\"` and the `merchantId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/merchant-customers/charge/{id}`"}},"/crm/contact-form/post/{app}/{name}":{"post":{"operationId":"CrmFormController_saveContactFormPost","summary":"Submit a contact form (multipart)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"app","required":true,"in":"path","schema":{"type":"string"},"description":"Application the form belongs to.","example":"website"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name.","example":"contact-us"},{"name":"type","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":"The submission result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"description":"Public. Accepts a website contact form as `multipart/form-data`, so a plain HTML `<form method=\"post\" enctype=\"multipart/form-data\">` can post straight here. Uploaded files are stored under `form-data/{app}/{name}/` and replaced in the body by `{ originalName, mimeType, extension, size, path, signedUrl }`; the rest is handled as the JSON variant.\n\n#### Signature\n\n```http\nPOST /crm/contact-form/post/{app}/{name} (app: string, name: string, type?: string, body) -> The submission result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/contact-form/json/{app}/{name}`","requestBody":{"description":"The submitted values and files.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","additionalProperties":true}}}}}},"/crm/contact-form/json/{app}/{name}":{"post":{"operationId":"CrmFormController_saveContactFormJSON","summary":"Submit a contact form as JSON","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"app","required":true,"in":"path","schema":{"type":"string"},"description":"Application the form belongs to.","example":"website"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name.","example":"contact-us"},{"name":"type","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":"The submission result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Error saving data: <reason> — The submission could not be stored.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Error saving data: <reason>","path":"/crm/contact-form/json/{app}/{name}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"description":"Public. The first post for a new `name` creates the `crm_form` (its schema generated from the body's shape) so later posts validate against it; each submission is stored in `form_submission`.\n\n#### Signature\n\n```http\nPOST /crm/contact-form/json/{app}/{name} (app: string, name: string, type?: string, body) -> The submission result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SAVE_FAILED | Error saving data: <reason> | The submission could not be stored. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/contact-form/post/{app}/{name}`","requestBody":{"description":"The submitted values.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Ada Lovelace","email":"ada@example.com","message":"When do you ship to Ireland?"}}}}}},"/crm/form/{name}":{"get":{"operationId":"CrmFormController_getCRMForm","summary":"Open a form","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name (or a publicly shared collection name).","example":"contact-us"},{"name":"code","required":false,"in":"query","schema":{"type":"string"},"description":"Access code or participant code."},{"name":"x-form-code","required":false,"in":"header","schema":{"type":"string"},"description":"Access code (alternative to `code`)."},{"name":"email","required":false,"in":"query","schema":{"type":"string"},"description":"For forms whose authenticationType is `email`."},{"name":"token","required":false,"in":"query","schema":{"type":"string"},"description":"Token from a magic link or one-time code email."},{"name":"x-form-token","required":false,"in":"header","schema":{"type":"string"},"description":"Link token (alternative to `token`)."}],"responses":{"200":{"description":"{ form, collection, schema, authenticationType, authentication, participant, submitter, session, displayName, logo }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"This form needs an access code. — accessMode `code` with no code, or `participants` with no code (\"This form is by invitation. Open it from the link you were sent.\"). Body `reason: \"code-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"This form needs an access code.","path":"/crm/form/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This form is not available for public access — An explicit `read` list leaves out Guest (a collection: \"This collection is not shared publicly\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This form is not available for public access","path":"/crm/form/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No form or collection found with name <name> — No crm_form and no collection by that name (a form bound to a missing collection: \"Collection not found\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No form or collection found with name <name>","path":"/crm/form/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"description":"Public. What a visitor needs to draw and send a form — never the internal record. The reply carries `form` (name, title, description, submitMessage, authenticationType, accessMode, status, dates, schema, pages), `collection` for a collection-bound form, the `schema` to render, `authentication` (`{ required, type, satisfied }`), `participant`, `submitter`, `session` (the pending submission a link/code opened, with any saved `values`), `displayName` and `logo`.\n\nGates, in order: the form (or a collection shared under that name) must exist; an explicit `read` list must include `Guest` (a form with no `read` list is open; a collection never is unless shared); the form must be open (status and start/end dates); `accessMode` `code` needs the form's access code and `participants` a participant's code; and `authenticationType` `magic-link` / `code` needs the token from the email, `email` asks for an address, `password` a signed-in customer. Refusals carry `{ message, reason, title }` so the page can ask for the right thing.\n\n#### Signature\n\n```http\nGET /crm/form/{name} (name: string, code?: string, email?: string, token?: string) -> { form, collection, schema, authenticationType, authentication, participant, submitter, session, displayName, logo }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FORM_NOT_FOUND | No form or collection found with name <name> | No crm_form and no collection by that name (a form bound to a missing collection: \"Collection not found\"). | — |\n| `403` | NOT_PUBLIC | This form is not available for public access | An explicit `read` list leaves out Guest (a collection: \"This collection is not shared publicly\"). | — |\n| `401` | CODE_REQUIRED | This form needs an access code. | accessMode `code` with no code, or `participants` with no code (\"This form is by invitation. Open it from the link you were sent.\"). Body `reason: \"code-required\"`. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/form/submit/{name}/{email}`\n- `POST /crm/form/{name}/access`"}},"/crm/form/{name}/access":{"post":{"operationId":"CrmFormController_requestFormAccess","summary":"Ask for a form link or code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name.","example":"grant-application"}],"responses":{"201":{"description":"{ sent: true, email, via, session }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid email address is required — `email` missing or has no @.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid email address is required","path":"/crm/form/{name}/access","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This form is not open yet. — The form is closed, ended, or outside its dates (body `reason`: not-open-yet | closed).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This form is not open yet.","path":"/crm/form/{name}/access","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No form named <name> — No crm_form with that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No form named <name>","path":"/crm/form/{name}/access","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"description":"Public. For a form whose authenticationType is `magic-link` or `code`: the person gives their email, is added to the participants, and the invitation with their link and code is emailed to them. Nothing about the form is revealed by asking.\n\n#### Signature\n\n```http\nPOST /crm/form/{name}/access (name: string, body) -> { sent: true, email, via, session }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_REQUIRED | A valid email address is required | `email` missing or has no @. | — |\n| `404` | FORM_NOT_FOUND | No form named <name> | No crm_form with that name. | — |\n| `403` | FORM_NOT_OPEN | This form is not open yet. | The form is closed, ended, or outside its dates (body `reason`: not-open-yet \\| closed). | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}},"example":{"email":"ada@example.com"}}}}}},"/crm/form/submit/{name}/{email}":{"post":{"operationId":"CrmFormController_submitCRMForm","summary":"Submit a form","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name.","example":"contact-us"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Submitter email, when known (also read from `body.email`).","example":"ada@example.com"}],"requestBody":{"description":"The submitted values, as a flat object keyed by the schema's field names.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Ada Lovelace","email":"ada@example.com","message":"When do you ship to Ireland?"}}}},"responses":{"201":{"description":"The saved submission (form_submission, or a record of the bound collection)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Form data is required — Empty body.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Form data is required","path":"/crm/form/submit/{name}/{email}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"This form needs an access code. — accessMode `code` with no code, or `participants` with no code (\"This form is by invitation. Open it from the link you were sent.\"). Body `reason: \"code-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"This form needs an access code.","path":"/crm/form/submit/{name}/{email}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This form is not available for public access — An explicit `read` list leaves out Guest (a collection: \"This collection is not shared publicly\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This form is not available for public access","path":"/crm/form/submit/{name}/{email}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No form or collection found with name <name> — No crm_form and no collection by that name (a form bound to a missing collection: \"Collection not found\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No form or collection found with name <name>","path":"/crm/form/submit/{name}/{email}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"description":"Public. Submits a completed form. The same gates as `GET /crm/form/{name}` apply first; then the identity the form asks for is checked for submitting, an explicit `create` list must include `Guest` (a collection must always grant it), and the body is validated against the schema — missing required fields come back as \"Please fill in <field titles>.\" The access code may be sent as `accessCode` in the body, `x-form-code` or `?code=`; a link token as `token`, `x-form-token` or `?token=`.\n\n#### Signature\n\n```http\nPOST /crm/form/submit/{name}/{email} (name: string, email: string, body) -> The saved submission (form_submission, or a record of the bound collection)\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Notification email to the form's `data.emails` is billed. An org without credit still stores the submission but sends nothing, so do not rely on the email as your only signal.\n- Guard the client against double submission. Disabling the button is not enough; a second submit event produces a second record.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | FORM_DATA_REQUIRED | Form data is required | Empty body. | — |\n| `404` | FORM_NOT_FOUND | No form or collection found with name <name> | No crm_form and no collection by that name (a form bound to a missing collection: \"Collection not found\"). | — |\n| `403` | NOT_PUBLIC | This form is not available for public access | An explicit `read` list leaves out Guest (a collection: \"This collection is not shared publicly\"). | — |\n| `401` | CODE_REQUIRED | This form needs an access code. | accessMode `code` with no code, or `participants` with no code (\"This form is by invitation. Open it from the link you were sent.\"). Body `reason: \"code-required\"`. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /crm/form/{name}`\n- `POST /client/forms/{name}/submit`"}},"/crm/form/{name}/invite":{"post":{"operationId":"CrmFormController_inviteParticipants","summary":"Invite a form's participants","description":"Staff only. Emails each participant their own link and access code (a code is minted for anyone without one). Pass `emails` to send to those participants only; omit it for everyone. Safe to repeat: a resend is marked as a reminder and never changes a code, so a link already sent keeps working. Uses the form's `invitationTemplate`, else `form-invitation`. A `new` form becomes `sent`.\n\n#### Signature\n\n```http\nPOST /crm/form/{name}/invite (name: string, body) -> { form, template, sent, failed, results: [{ email, sent, reminder, inviteCount, link }], participants }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FORM_NOT_FOUND | No form named <name> | No crm_form with that name. | — |\n| `409` | NO_PARTICIPANTS | This form has no participants to invite. | The form lists no participants. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name.","example":"grant-application"}],"responses":{"201":{"description":"{ form, template, sent, failed, results: [{ email, sent, reminder, inviteCount, link }], participants }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No form named <name> — No crm_form with that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No form named <name>","path":"/crm/form/{name}/invite","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This form has no participants to invite. — The form lists no participants.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This form has no participants to invite.","path":"/crm/form/{name}/invite","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emails":{"type":"array","items":{"type":"string"}}}},"example":{"emails":["ada@example.com"]}}}}}},"/crm/forms/overview":{"get":{"operationId":"CrmFormController_getFormsOverview","summary":"Forms overview","description":"Staff only. Everything the Forms dashboard shows: totals (forms, public, open, collection-bound, on a workflow, in a workflow now, started-not-finished, submissions, submissions in the period, awaiting review), counts by status, a row per form with its own numbers, and the most recent submissions. Counts cover inline forms (`form_submission`) and collection-bound forms (records of the bound collection).\n\n#### Signature\n\n```http\nGET /crm/forms/overview (period?: string) -> { period, since, totals, byStatus, forms, recent }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"period","required":false,"in":"query","schema":{"type":"string","enum":["today","week","month","year"],"default":"week"}}],"responses":{"200":{"description":"{ period, since, totals, byStatus, forms, recent }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"]}},"/crm/forms/submissions":{"get":{"operationId":"CrmFormController_listAllSubmissions","summary":"All form submissions","description":"Staff only. Submissions across every form, newest first, paged. Each row carries the form it answers and, when it is on a workflow, `workflowInfo` (workflow, named stage, task, stage history).\n\n#### Signature\n\n```http\nGET /crm/forms/submissions (formId?: string, status?: string, email?: string, inWorkflow?: string, page?: integer, pageSize?: integer) -> { data, total, page, pageSize }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"formId","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string"}},{"name":"email","required":false,"in":"query","schema":{"type":"string"}},{"name":"inWorkflow","required":false,"in":"query","schema":{"type":"string","enum":["true"]}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"{ data, total, page, pageSize }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"]}},"/crm/collection-form/{number}/{demo}-email?":{"post":{"operationId":"CrmFormController_sendCRMForm","summary":"Send a form to its participants (legacy)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"number","required":true,"in":"path","schema":{"type":"string"},"description":"Ignored.","example":"4821"},{"name":"demo-email","required":true,"in":"path","schema":{"type":"string"}},{"name":"demo","in":"path","required":true,"description":"Ignored.","schema":{"type":"string"},"example":"ada"}],"responses":{"201":{"description":"Same as the invite route","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"form name is required — No form name in the body.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"form name is required","path":"/crm/collection-form/{number}/{demo}-email?","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"description":"Staff. Kept for older clients: takes the form name from `body.name` (or `body.form.data.name`) and does exactly what `POST /crm/form/{name}/invite` does, including `body.emails`. The path segments are ignored. The route is declared as `collection-form/:number/:demo-email?`, which Express reads as a parameter `demo` followed by the literal text `-email?` — prefer the invite route.\n\n#### Signature\n\n```http\nPOST /crm/collection-form/{number}/{demo}-email? (number: string, demo: string, body) -> Same as the invite route\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAME_REQUIRED | form name is required | No form name in the body. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/form/{name}/invite`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"emails":{"type":"array","items":{"type":"string"}}}},"example":{"name":"grant-application"}}}}}},"/crm/form/{id}":{"delete":{"operationId":"CrmFormController_deleteForm","summary":"Delete a form","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Forms"],"description":"Staff only. Deletes the form definition (`crm_form`). Its submissions are not deleted.\n\n#### Signature\n\n```http\nDELETE /crm/form/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `User`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/client/forms/{name}":{"get":{"operationId":"CrmFormClientController_getForm","summary":"Open a form (customer)","description":"The customer-facing twin of `GET /crm/form/{name}`: same gates and same reply. A signed-in customer is taken from the token (never from the request), which satisfies a `password` form and is the submitter.\n\n#### Signature\n\n```http\nGET /client/forms/{name} (name: string, code?: string, email?: string, token?: string) -> As `GET /crm/form/{name}`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | FORM_NOT_FOUND | No form or collection found with name <name> | No crm_form and no collection by that name (a form bound to a missing collection: \"Collection not found\"). | — |\n| `403` | NOT_PUBLIC | This form is not available for public access | An explicit `read` list leaves out Guest (a collection: \"This collection is not shared publicly\"). | — |\n| `401` | CODE_REQUIRED | This form needs an access code. | accessMode `code` with no code, or `participants` with no code (\"This form is by invitation. Open it from the link you were sent.\"). Body `reason: \"code-required\"`. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /crm/form/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name.","example":"contact-us"},{"name":"code","required":false,"in":"query","schema":{"type":"string"}},{"name":"x-form-code","required":false,"in":"header","schema":{"type":"string"}},{"name":"email","required":false,"in":"query","schema":{"type":"string"}},{"name":"token","required":false,"in":"query","schema":{"type":"string"}},{"name":"x-form-token","required":false,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"As `GET /crm/form/{name}`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"This form needs an access code. — accessMode `code` with no code, or `participants` with no code (\"This form is by invitation. Open it from the link you were sent.\"). Body `reason: \"code-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"This form needs an access code.","path":"/client/forms/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This form is not available for public access — An explicit `read` list leaves out Guest (a collection: \"This collection is not shared publicly\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This form is not available for public access","path":"/client/forms/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No form or collection found with name <name> — No crm_form and no collection by that name (a form bound to a missing collection: \"Collection not found\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No form or collection found with name <name>","path":"/client/forms/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Forms Client"]}},"/client/forms/{name}/access":{"post":{"operationId":"CrmFormClientController_requestAccess","summary":"Ask for a form link or code (customer)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name."}],"responses":{"201":{"description":"{ sent: true, email, via, session }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Forms Client"],"description":"Same as `POST /crm/form/{name}/access`.\n\n#### Signature\n\n```http\nPOST /client/forms/{name}/access (name: string, body) -> { sent: true, email, via, session }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/form/{name}/access`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}}}}}}},"/client/forms/{name}/submit":{"post":{"operationId":"CrmFormClientController_submit","summary":"Submit a form (customer)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Form name."},{"name":"email","required":false,"in":"query","schema":{"type":"string"}},{"name":"code","in":"query","required":false,"schema":{"type":"string"}},{"name":"token","in":"query","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The saved submission","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Forms Client"],"description":"Same as `POST /crm/form/submit/{name}/{email}`, with the submitter email as `?email=` instead of a path segment.\n\n#### Signature\n\n```http\nPOST /client/forms/{name}/submit (name: string, email?: string, code?: string, token?: string, body) -> The saved submission\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/form/submit/{name}/{email}`"}},"/crm/signed-documents":{"get":{"operationId":"SignedDocumentController_list","summary":"List signed documents","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Signed documents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"The documents sent for signature, with their current state.\n\n#### Signature\n\n```http\nGET /crm/signed-documents () -> Signed documents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/signed-documents/{id}`"},"post":{"operationId":"SignedDocumentController_create","summary":"Create a signed document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Creates a document for signature. It starts unsent — sending it is a separate step, so it can be reviewed first.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents (body) -> The created document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/send`","requestBody":{"description":"The document to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"title":{"type":"string","example":"Storage agreement"},"signers":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Who must sign."},"content":{"type":"string"}}},"example":{"title":"Storage agreement","signers":[{"email":"ada@example.com","name":"Ada Lovelace"}]}}}}}},"/crm/signed-documents/context/{datatype}/{id}":{"get":{"operationId":"SignedDocumentController_listForContext","summary":"Get documents for a record","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The record's datatype.","example":"stowbo_booking"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Documents attached to that record","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"The signature documents attached to another record — the contracts on a booking, the waivers on a rental. Use this rather than storing document ids on the record yourself.\n\n#### Signature\n\n```http\nGET /crm/signed-documents/context/{datatype}/{id} (datatype: string, id: string) -> Documents attached to that record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents`"}},"/crm/signed-documents/{id}":{"get":{"operationId":"SignedDocumentController_get","summary":"Get a signed document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/crm/signed-documents/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Fetches one document with its signature status and audit trail.\n\n#### Signature\n\n```http\nGET /crm/signed-documents/{id} (id: string) -> The document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/send`"},"delete":{"operationId":"SignedDocumentController_remove","summary":"Delete a signed document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/crm/signed-documents/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Deletes a document and its audit trail. For anything already signed, **void it instead** — the signature record is legal evidence and deleting it destroys that.\n\n#### Signature\n\n```http\nDELETE /crm/signed-documents/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Destroys the signature audit trail. Use `void` for anything signed.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/void`"}},"/crm/signed-documents/{id}/update":{"post":{"operationId":"SignedDocumentController_update","summary":"Update a signed document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The updated document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/crm/signed-documents/{id}/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Updates a document before it is signed. Editing one that has already been signed would invalidate the signature — change it before sending, or void it and start again.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents/{id}/update (id: string, body) -> The updated document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/void`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Storage agreement (2026)"}}}}}},"/crm/signed-documents/{id}/send":{"post":{"operationId":"SignedDocumentController_send","summary":"Send a document for signature","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/crm/signed-documents/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Sends the document to its signers, each receiving a portal link. Once sent, the content should not change.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents/{id}/send (id: string) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/remind`"}},"/crm/signed-documents/{id}/remind":{"post":{"operationId":"SignedDocumentController_remind","summary":"Remind signers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/crm/signed-documents/{id}/remind","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Re-sends the signature request to anyone who has not yet signed. Sends every time it is called — there is no cooldown.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents/{id}/remind (id: string) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/send`"}},"/crm/signed-documents/{id}/cancel":{"post":{"operationId":"SignedDocumentController_cancel","summary":"Cancel a signature request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The cancelled document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/crm/signed-documents/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Withdraws a document from signature before it is complete. Portal links stop working; the record is kept.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents/{id}/cancel (id: string) -> The cancelled document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/void`"}},"/crm/signed-documents/{id}/void":{"post":{"operationId":"SignedDocumentController_voidIt","summary":"Void a signed document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The voided document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that identifier.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/crm/signed-documents/{id}/void","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Voids a document that has already been signed, marking it no longer in force while **keeping the signature record intact**.\n\nThis is the correct way to retire a signed agreement: cancel applies before signature, delete destroys the evidence, void preserves it.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents/{id}/void (id: string) -> The voided document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/{id}/cancel`"}},"/crm/signed-documents/portal/{token}":{"get":{"operationId":"SignedDocumentController_getByToken","summary":"Get a document from the signing portal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"token","required":true,"in":"path","schema":{"type":"string"},"description":"Signing token from the invitation email.","example":"sig_9k2m4h1p7q"}],"responses":{"200":{"description":"The document to sign","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"The **public** read a signer uses to view their document, authenticated by the token in their email rather than by an account.\n\nThe token is the only credential — anyone holding the link can read the document.\n\n#### Signature\n\n```http\nGET /crm/signed-documents/portal/{token} (token: string) -> The document to sign\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated. Treat signing links as secrets.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/portal/{token}/sign`"}},"/crm/signed-documents/portal/{token}/sign":{"post":{"operationId":"SignedDocumentController_sign","summary":"Sign a document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"token","required":true,"in":"path","schema":{"type":"string"},"description":"Signing token.","example":"sig_9k2m4h1p7q"}],"requestBody":{"description":"The signature.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"signature":{"type":"string","description":"Signature data."},"name":{"type":"string","example":"Ada Lovelace"}}},"example":{"name":"Ada Lovelace","signature":"data:image/png;base64,…"}}}},"responses":{"201":{"description":"The signed document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Records a signature from the signing portal. **Public**, authenticated only by the token, and irreversible — a signed document can be voided but not unsigned.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents/portal/{token}/sign (token: string, body) -> The signed document\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Irreversible. The signature and its metadata become the audit record.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/portal/{token}/decline`"}},"/crm/signed-documents/portal/{token}/decline":{"post":{"operationId":"SignedDocumentController_decline","summary":"Decline to sign","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"token","required":true,"in":"path","schema":{"type":"string"},"description":"Signing token.","example":"sig_9k2m4h1p7q"}],"responses":{"201":{"description":"The declined document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Documents"],"description":"Records that a signer declined, with their reason. The document stops awaiting them and the sender is informed.\n\n#### Signature\n\n```http\nPOST /crm/signed-documents/portal/{token}/decline (token: string, body) -> The declined document\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents/portal/{token}/sign`","requestBody":{"description":"Why they declined.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Terms need review by our legal team"}}},"example":{"reason":"Terms need review by our legal team"}}}}}},"/crm/shared-accounts":{"get":{"operationId":"SharedAccountsController_list","summary":"List shared accounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"{ total, page, pageSize, data }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"Every shared account in the org, assembled: the account, its members (with names), pending invitations, `memberCount` and `needsManager` (members but no manager). An account holding only pending invitations is listed too. `search` matches the account, any member or any of its groups. Sorted by member count, then name.\n\n#### Signature\n\n```http\nGET /crm/shared-accounts (search?: string, page?: integer, pageSize?: integer) -> { total, page, pageSize, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/crm/shared-accounts/{accountId}":{"get":{"operationId":"SharedAccountsController_get","summary":"Get a shared account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"One account, assembled as in the list.\n\n#### Signature\n\n```http\nGET /crm/shared-accounts/{accountId} (accountId: string) -> The account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/crm/shared-accounts/{accountId}/orders":{"get":{"operationId":"SharedAccountsController_orders","summary":"Shared account orders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paged orders","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"Orders placed on the account by any member.\n\n#### Signature\n\n```http\nGET /crm/shared-accounts/{accountId}/orders (accountId: string, page?: integer, pageSize?: integer) -> Paged orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/crm/shared-accounts/{accountId}/activity":{"get":{"operationId":"SharedAccountsController_activity","summary":"Shared account activity","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"{ leads, deals, lost, tickets, messages, totals }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"The account's open leads, deals (leads at a won stage), lost count, tickets and the latest 100 conversations, with totals (open value, won value, open tickets, messages).\n\n#### Signature\n\n```http\nGET /crm/shared-accounts/{accountId}/activity (accountId: string) -> { leads, deals, lost, tickets, messages, totals }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/crm/shared-accounts/{accountId}/members":{"post":{"operationId":"SharedAccountsController_addMember","summary":"Add someone to a shared account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-host","required":false,"in":"header","schema":{"type":"string"},"description":"Storefront host for the invitation link."},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"`{ added, reinstated, customerId }` or `{ invited, resent, email }`, plus the assembled `account`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid email is required — `email` missing or has no @.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid email is required","path":"/crm/shared-accounts/{accountId}/members","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"An email that already belongs to a customer is associated straight away (a suspended member is reinstated) and told by email; a new email gets a pending invitation whose sign-up link points at `x-client-host`. `role` defaults to buyer.\n\n#### Signature\n\n```http\nPOST /crm/shared-accounts/{accountId}/members (accountId: string, body) -> `{ added, reinstated, customerId }` or `{ invited, resent, email }`, plus the assembled `account`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_REQUIRED | A valid email is required | `email` missing or has no @. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"role":{"type":"string","enum":["manager","buyer"]},"message":{"type":"string"}}},"example":{"email":"buyer@acme.com","role":"manager"}}}}}},"/crm/shared-accounts/{accountId}/members/{associationId}":{"put":{"operationId":"SharedAccountsController_updateMember","summary":"Change a shared account member","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"associationId","required":true,"in":"path","schema":{"type":"string"},"description":"customer_association sk."}],"responses":{"200":{"description":"The assembled account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Nothing to change — pass a role or a status — Neither field given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Nothing to change — pass a role or a status","path":"/crm/shared-accounts/{accountId}/members/{associationId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Member not found on this account — The association does not exist or belongs to another account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Member not found on this account","path":"/crm/shared-accounts/{accountId}/members/{associationId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"Changes `role` or `status` (suspend / reinstate). Unlike the customer route, staff may leave an account with no manager — the returned account says so with `needsManager`.\n\n#### Signature\n\n```http\nPUT /crm/shared-accounts/{accountId}/members/{associationId} (accountId: string, associationId: string, body) -> The assembled account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | MEMBER_NOT_FOUND | Member not found on this account | The association does not exist or belongs to another account. | — |\n| `400` | NOTHING_TO_CHANGE | Nothing to change — pass a role or a status | Neither field given. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["manager","buyer"]},"status":{"type":"string","enum":["active","suspended"]}}},"example":{"role":"manager"}}}}},"delete":{"operationId":"SharedAccountsController_removeMember","summary":"Remove a shared account member","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"associationId","required":true,"in":"path","schema":{"type":"string"},"description":"customer_association sk."}],"responses":{"200":{"description":"The assembled account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Member not found on this account — The association does not exist or belongs to another account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Member not found on this account","path":"/crm/shared-accounts/{accountId}/members/{associationId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"Unlinks the member. Their past orders are untouched.\n\n#### Signature\n\n```http\nDELETE /crm/shared-accounts/{accountId}/members/{associationId} (accountId: string, associationId: string) -> The assembled account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | MEMBER_NOT_FOUND | Member not found on this account | The association does not exist or belongs to another account. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/crm/shared-accounts/{accountId}/invites/{invitationId}/cancel":{"post":{"operationId":"SharedAccountsController_cancelInvite","summary":"Withdraw an invitation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"invitationId","required":true,"in":"path","schema":{"type":"string"},"description":"customer_invitation sk."}],"responses":{"201":{"description":"The assembled account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Invitation not found on this account — No such pending invitation on this account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Invitation not found on this account","path":"/crm/shared-accounts/{accountId}/invites/{invitationId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Shared accounts"],"description":"#### Signature\n\n```http\nPOST /crm/shared-accounts/{accountId}/invites/{invitationId}/cancel (accountId: string, invitationId: string) -> The assembled account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INVITE_NOT_FOUND | Invitation not found on this account | No such pending invitation on this account. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/analytics":{"get":{"operationId":"AnalyticsController_getAnalytics","summary":"Get analytics data","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"type":"string","enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"]},"description":"Dashboard type. Omit for all but email.","example":"storefront"},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Analytics data","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"orgId is required — The `orgid` header is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"orgId is required","path":"/analytics","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get analytics — The figures could not be computed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get analytics","path":"/analytics","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"The general analytics read. `type` selects one dashboard; omitting it returns `{ website, blog, workflow, storefront, tickets, leads, automation, users }` in one response (heavier, and `email` is only available by naming it). Unlike the per-area routes, this read carries no previous-window `comparison`.\n\n#### Signature\n\n```http\nGET /analytics (type?: string, startDate?: string, endDate?: string) -> Analytics data\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORGID_REQUIRED | orgId is required | The `orgid` header is missing. | Send the `orgid` header. |\n| `500` | ANALYTICS_FAILED | Failed to get analytics | The figures could not be computed. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics/website`"}},"/analytics/filter-options":{"get":{"operationId":"AnalyticsController_getFilterOptions","summary":"Get filter options","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Filter options","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"orgId is required — The `orgid` header is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"orgId is required","path":"/analytics/filter-options","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get filter options — The figures could not be computed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get filter options","path":"/analytics/filter-options","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Distinct sites, domains and hosts seen in web visit data over the last 90 days — what populates a dashboard's filter dropdowns. Derived from observed traffic, so a site with no visits in that window will not appear. Up to 100 of each.\n\n#### Signature\n\n```http\nGET /analytics/filter-options () -> Filter options\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORGID_REQUIRED | orgId is required | The `orgid` header is missing. | Send the `orgid` header. |\n| `500` | ANALYTICS_FAILED | Failed to get filter options | The figures could not be computed. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics/website`"}},"/analytics/live-view":{"get":{"operationId":"AnalyticsController_getLiveView","summary":"Get Live View","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"minutes","required":false,"in":"query","description":"Window in minutes, default 15, minimum 1. Wider is less \"live\".","schema":{"type":"integer"},"example":5}],"responses":{"200":{"description":"Current visitors and metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"orgId is required — The `orgid` header is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"orgId is required","path":"/analytics/live-view","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get live view — The figures could not be computed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get live view","path":"/analytics/live-view","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Unique visitors on the site right now, with real-time aggregated metrics. `minutes` sets how far back \"now\" reaches — a wider window shows more people but stops being live.\n\nBuilt from the web-visit timeseries, so a visitor appears only once their first event has been recorded. Bots, scanners and probes are never recorded, so they never appear here. Refreshed about every 15 seconds; up to 500 visitors, most recently seen first.\n\n#### Signature\n\n```http\nGET /analytics/live-view (minutes?: integer) -> Current visitors and metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORGID_REQUIRED | orgId is required | The `orgid` header is missing. | Send the `orgid` header. |\n| `500` | ANALYTICS_FAILED | Failed to get live view | The figures could not be computed. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics/live-view/{deviceId}/journey`"}},"/analytics/live-view/{deviceId}/journey":{"get":{"operationId":"AnalyticsController_getLiveViewJourney","summary":"Get a visitor journey","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"},"description":"Device identifier from Live View.","example":"dev_7Kq2M9"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":100}],"responses":{"200":{"description":"The journey","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"orgId is required — The `orgid` header is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"orgId is required","path":"/analytics/live-view/{deviceId}/journey","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get device journey — The figures could not be computed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get device journey","path":"/analytics/live-view/{deviceId}/journey","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"The full timeline for one visitor — every page and event in order. `deviceId` identifies a browser, not a person: the same human on a phone and a laptop is two device ids, and a cleared browser is a new one. Newest event first, at most 500 (`limit`, default 100). Refreshed about every 15 seconds, like Live View.\n\n#### Signature\n\n```http\nGET /analytics/live-view/{deviceId}/journey (deviceId: string, limit?: integer) -> The journey\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORGID_REQUIRED | orgId is required | The `orgid` header is missing. | Send the `orgid` header. |\n| `500` | ANALYTICS_FAILED | Failed to get device journey | The figures could not be computed. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics/live-view`"}},"/analytics/website":{"get":{"operationId":"AnalyticsController_getWebsiteAnalytics","summary":"Get website analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Website analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get website analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get website analytics","path":"/analytics/website","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Traffic and engagement across the org's sites over a date range.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/website (startDate?: string, endDate?: string) -> Website analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get website analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/blog":{"get":{"operationId":"AnalyticsController_getBlogAnalytics","summary":"Get blog analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Blog analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get blog analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get blog analytics","path":"/analytics/blog","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Post readership and engagement. A visit counts as a view of a post when the last segment of its URL is one of the org’s post slugs (the post’s `name` when it has no slug) — whatever prefix the site gives posts (`/blog/`, `/news/`, …). `topPosts` are the ten most-viewed posts in the range; `overview.totalViews` counts every post view in the range. Category and author figures come from the posts themselves.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/blog (startDate?: string, endDate?: string) -> Blog analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get blog analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/workflow":{"get":{"operationId":"AnalyticsController_getWorkflowAnalytics","summary":"Get workflow analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Workflow analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get workflow analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get workflow analytics","path":"/analytics/workflow","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Pipeline throughput and stage timings from the workflow engine.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/workflow (startDate?: string, endDate?: string) -> Workflow analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get workflow analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/storefront":{"get":{"operationId":"AnalyticsController_getStorefrontAnalytics","summary":"Get storefront analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Storefront analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get storefront analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get storefront analytics","path":"/analytics/storefront","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Orders, revenue and conversion for the storefront.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/storefront (startDate?: string, endDate?: string) -> Storefront analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get storefront analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/storefront/orders":{"post":{"operationId":"AnalyticsController_getOrderDashboard","summary":"Get the order dashboard","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Order analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"orgId is required — The `orgid` header is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"orgId is required","path":"/analytics/storefront/orders","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get order dashboard analytics — The figures could not be computed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get order dashboard analytics","path":"/analytics/storefront/orders","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Order analytics with a richer selector than the query-string dashboards: `period` picks a named window (`today`, `week`, `month`, `year`), or give explicit dates. `recentLimit` caps how many recent orders come back alongside the aggregates.\n\nA POST because of the body, not because it changes anything — it is a read.\n\n#### Signature\n\n```http\nPOST /analytics/storefront/orders (body) -> Order analytics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only despite being a POST.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORGID_REQUIRED | orgId is required | The `orgid` header is missing. | Send the `orgid` header. |\n| `500` | ANALYTICS_FAILED | Failed to get order dashboard analytics | The figures could not be computed. | Retry; escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics/storefront`","requestBody":{"description":"The window.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"string","enum":["today","week","month","year"],"example":"month"},"startDate":{"type":"string","description":"Overrides `period`.","example":"2026-08-01"},"endDate":{"type":"string","example":"2026-08-31"},"recentLimit":{"type":"integer","description":"How many recent orders to include.","example":10}}},"examples":{"namedPeriod":{"summary":"A named period","value":{"period":"month","recentLimit":10}},"explicit":{"summary":"An explicit range","value":{"startDate":"2026-08-01","endDate":"2026-08-31"}}}}}}}},"/analytics/tickets":{"get":{"operationId":"AnalyticsController_getTicketAnalytics","summary":"Get ticket analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Ticket analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get ticket analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get ticket analytics","path":"/analytics/tickets","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Support ticket volume, resolution time and backlog.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/tickets (startDate?: string, endDate?: string) -> Ticket analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get ticket analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/attribution":{"get":{"operationId":"AnalyticsController_getAttributionAnalytics","summary":"Get revenue attribution","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"{ period, totals, bySource, byMedium, byCampaign }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"period":{"startDate":"2026-08-01T00:00:00.000Z","endDate":"2026-08-31T00:00:00.000Z"},"totals":{"orders":120,"revenue":9400,"attributedOrders":84,"attributedRevenue":7010,"coverage":70,"unattributedRevenue":2390},"bySource":[{"name":"facebook","orders":40,"revenue":3200,"customers":36,"sessions":900,"revenuePerSession":3.56}],"byMedium":[{"name":"cpc","orders":50,"revenue":4100,"customers":44}],"byCampaign":[{"name":"fall-sale","orders":30,"revenue":2500,"customers":28,"sessions":610,"revenuePerSession":4.1}]}}}},"400":{"description":"Organization ID is required — The `orgid` header is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Organization ID is required","path":"/analytics/attribution","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"],"description":"Revenue by campaign source, medium and campaign, read from what the campaign page handed over at checkout (`sf_order.data.attribution`) — not a join back over visits, which are purged after six months. Cancelled, refunded and draft orders are not counted.\n\n`bySource` and `byCampaign` also carry `sessions` from web visits (their `utmSource` / `utmCampaign`), so a source with traffic and no revenue still shows, with `revenuePerSession`. Rows sort by revenue, then sessions. `totals.coverage` is the percentage of orders that carried a source — a low number means campaign pages are not handing their source over at checkout.\n\nWithout dates the window is the last 30 days. Cached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/attribution (startDate?: string, endDate?: string) -> { period, totals, bySource, byMedium, byCampaign }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORGID_REQUIRED | Organization ID is required | The `orgid` header is missing. | Send the `orgid` header. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /analytics/storefront`"}},"/analytics/leads":{"get":{"operationId":"AnalyticsController_getLeadsAnalytics","summary":"Get leads analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Leads analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get leads analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get leads analytics","path":"/analytics/leads","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Lead volume, source and conversion.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/leads (startDate?: string, endDate?: string) -> Leads analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get leads analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/automation":{"get":{"operationId":"AnalyticsController_getAutomationAnalytics","summary":"Get automation analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Automation analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get automation analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get automation analytics","path":"/analytics/automation","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Automation run counts, successes and failures.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/automation (startDate?: string, endDate?: string) -> Automation analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get automation analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/users":{"get":{"operationId":"AnalyticsController_getUserAccountAnalytics","summary":"Get user account analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"User account analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get user account analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get user account analytics","path":"/analytics/users","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Account signups, activity and retention.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/users (startDate?: string, endDate?: string) -> User account analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get user account analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/email":{"get":{"operationId":"AnalyticsController_getEmailAnalytics","summary":"Get email analytics","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"],"type":"string"}},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Email analytics with the previous-window comparison","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"overview":{"type":"object","additionalProperties":true},"period":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"}}},"comparison":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"overview":{"type":"object","additionalProperties":{"type":"object","properties":{"previous":{"type":"number"},"changePercent":{"type":"number","nullable":true},"direction":{"type":"string","enum":["up","down","flat"]},"sentiment":{"type":"string","enum":["good","bad","neutral"]}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to get email analytics — The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to get email analytics","path":"/analytics/email","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Email send and engagement figures.\n\nEvery dashboard is computed twice — for the requested window and for the window of the same length just before it — and returned with `period` (the window used) and `comparison: { startDate, endDate, overview }`, where each numeric `overview` figure gets `{ previous, changePercent, direction: up|down|flat, sentiment: good|bad|neutral }`. `changePercent` is `null` when the previous value was 0. Without dates the window is the last 30 days.\n\nCached for about five minutes: a reload inside that window returns the same figures.\n\n#### Signature\n\n```http\nGET /analytics/email (startDate?: string, endDate?: string) -> Email analytics with the previous-window comparison\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to get email analytics | The figures could not be computed — or the `orgid` header is missing (the handler reports that as this 500, not a 400). | Check the `orgid` header; otherwise retry and escalate if it persists. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/analytics/export":{"post":{"operationId":"AnalyticsController_exportAnalytics","summary":"Export analytics data","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-31"},{"name":"type","required":false,"in":"query","schema":{"type":"string","enum":["website","blog","workflow","storefront","tickets","leads","automation","users","email"]},"description":"Dashboard. Omit for all but email.","example":"storefront"},{"name":"metrics","required":false,"in":"query","schema":{"type":"array","items":{"type":"string"}}},{"name":"demo","required":false,"in":"query","description":"Use demo data instead of real data","schema":{"type":"boolean"}},{"name":"format","required":false,"in":"query","schema":{"type":"string","enum":["csv","json","pdf"],"default":"csv"},"description":"Echoed back; does not change the payload yet."}],"responses":{"201":{"description":"{ format, data, exportedAt }","content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string"},"data":{"type":"object","additionalProperties":true},"exportedAt":{"type":"string"}}},"example":{"format":"csv","data":{"overview":{}},"exportedAt":"2026-09-29T14:00:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to export analytics — The figures could not be computed — or the `orgid` header is missing (reported as this 500).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to export analytics","path":"/analytics/export","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Analytics"],"description":"Returns the same figures as `GET /analytics` for the dashboard and date range, wrapped as `{ format, data, exportedAt }`. The selector travels in the **query string**, not a body. No file is produced yet: `format` is echoed back and `data` is always JSON, whatever format is asked for.\n\n#### Signature\n\n```http\nPOST /analytics/export (type?: string, startDate?: string, endDate?: string, format?: string) -> { format, data, exportedAt }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | ANALYTICS_FAILED | Failed to export analytics | The figures could not be computed — or the `orgid` header is missing (reported as this 500). | Check the `orgid` header; otherwise retry. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /analytics`"}},"/stats/{datatype}/{id}/like":{"post":{"operationId":"StatsController_like","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Like a record","description":"Records that the caller likes a record.\n\nA per-caller toggle: send `action: \"remove\"` to undo it. Repeating `add` does not double-count — the signal is recorded once per caller.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/like (datatype: string, id: string, body) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/{datatype}/{id}`","requestBody":{"description":"Whether to add or remove the signal. Defaults to `add`.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["add","remove"],"description":"Defaults to `add`.","example":"add"}}},"examples":{"add":{"summary":"Add the signal","value":{"action":"add"}},"remove":{"summary":"Remove it","value":{"action":"remove"}}}}}}}},"/stats/{datatype}/{id}/dislike":{"post":{"operationId":"StatsController_dislike","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Dislike a record","description":"Records that the caller dislikes a record. Separate from removing a like — a dislike is its own signal.\n\nA per-caller toggle: send `action: \"remove\"` to undo it. Repeating `add` does not double-count — the signal is recorded once per caller.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/dislike (datatype: string, id: string, body) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/{datatype}/{id}`","requestBody":{"description":"Whether to add or remove the signal. Defaults to `add`.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["add","remove"],"description":"Defaults to `add`.","example":"add"}}},"examples":{"add":{"summary":"Add the signal","value":{"action":"add"}},"remove":{"summary":"Remove it","value":{"action":"remove"}}}}}}}},"/stats/{datatype}/{id}/bookmark":{"post":{"operationId":"StatsController_bookmark","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Bookmark a record","description":"Saves a record to the caller's bookmarks.\n\nA per-caller toggle: send `action: \"remove\"` to undo it. Repeating `add` does not double-count — the signal is recorded once per caller.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/bookmark (datatype: string, id: string, body) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/{datatype}/{id}`","requestBody":{"description":"Whether to add or remove the signal. Defaults to `add`.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["add","remove"],"description":"Defaults to `add`.","example":"add"}}},"examples":{"add":{"summary":"Add the signal","value":{"action":"add"}},"remove":{"summary":"Remove it","value":{"action":"remove"}}}}}}}},"/stats/{datatype}/{id}/follow":{"post":{"operationId":"StatsController_follow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Follow a record","description":"Follows a record, so the caller is tracked as interested in it.\n\nA per-caller toggle: send `action: \"remove\"` to undo it. Repeating `add` does not double-count — the signal is recorded once per caller.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/follow (datatype: string, id: string, body) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/{datatype}/{id}`","requestBody":{"description":"Whether to add or remove the signal. Defaults to `add`.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["add","remove"],"description":"Defaults to `add`.","example":"add"}}},"examples":{"add":{"summary":"Add the signal","value":{"action":"add"}},"remove":{"summary":"Remove it","value":{"action":"remove"}}}}}}}},"/stats/{datatype}/{id}/favorite":{"post":{"operationId":"StatsController_favorite","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Favorite a record","description":"Marks a record as one of the caller's favourites.\n\nA per-caller toggle: send `action: \"remove\"` to undo it. Repeating `add` does not double-count — the signal is recorded once per caller.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/favorite (datatype: string, id: string, body) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/{datatype}/{id}`","requestBody":{"description":"Whether to add or remove the signal. Defaults to `add`.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["add","remove"],"description":"Defaults to `add`.","example":"add"}}},"examples":{"add":{"summary":"Add the signal","value":{"action":"add"}},"remove":{"summary":"Remove it","value":{"action":"remove"}}}}}}}},"/stats/{datatype}/{id}/rating":{"post":{"operationId":"StatsController_rating","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Rate a record","description":"Records the caller's numeric rating. `action: \"update\"` changes an existing rating rather than adding a second — send it when the caller has already rated, or the aggregate counts them twice.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/rating (datatype: string, id: string, body) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Use `update` to change an existing rating.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/{datatype}/{id}`","requestBody":{"description":"The rating.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["value"],"properties":{"value":{"type":"number","description":"The rating.","example":4},"action":{"type":"string","enum":["add","update"],"description":"Defaults to `add`.","example":"add"}}},"example":{"value":4}}}}}},"/stats/{datatype}/{id}/reaction":{"post":{"operationId":"StatsController_reaction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"React to a record","description":"Records a named reaction — the open-ended signal, where like and favourite are fixed ones. The reaction name is whatever the caller sends.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/reaction (datatype: string, id: string, body) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stats/{datatype}/{id}/like`","requestBody":{"description":"The reaction.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","description":"The reaction name.","example":"celebrate"},"action":{"type":"string","enum":["add","remove"],"example":"add"}}},"example":{"value":"celebrate"}}}}}},"/stats/{datatype}/{id}/view":{"post":{"operationId":"StatsController_view","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Record a view","description":"Increments the record's view count. A **counter, not a toggle** — every call adds one, so a client that fires it on each render inflates the figure.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/view (datatype: string, id: string) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Increments on every call.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stats/{datatype}/{id}/share`"}},"/stats/{datatype}/{id}/share":{"post":{"operationId":"StatsController_share","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The updated stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Record a share","description":"Increments the record's share count. Also a counter — it records that a share was initiated, not that anyone received it.\n\n#### Signature\n\n```http\nPOST /stats/{datatype}/{id}/share (datatype: string, id: string) -> The updated stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Increments on every call.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stats/{datatype}/{id}/view`"}},"/stats/{datatype}/{id}":{"get":{"operationId":"StatsController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"200":{"description":"The stats","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Get a record's stats","description":"Aggregate signals for one record — counts, average rating, and the caller's own state where relevant.\n\n#### Signature\n\n```http\nGET /stats/{datatype}/{id} (datatype: string, id: string) -> The stats\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/by-resource/{datatype}/{id}`"}},"/stats/by-resource/{datatype}/{id}":{"get":{"operationId":"StatsController_byResource","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"200":{"description":"Signals on the record","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Get stats by resource","description":"The signals recorded against one record, listed rather than aggregated — who liked it, who bookmarked it.\n\n#### Signature\n\n```http\nGET /stats/by-resource/{datatype}/{id} (datatype: string, id: string) -> Signals on the record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/by-customer/{datatype}/{id}`"}},"/stats/by-customer/{datatype}/{id}":{"get":{"operationId":"StatsController_byCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType. Optional.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id. Optional.","example":"PRD-4821"}],"responses":{"200":{"description":"The caller's signals","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stats"],"summary":"Get stats by customer","description":"The signals the **calling customer** has recorded, optionally narrowed to a datatype or a single record. Both path segments are optional — omit them for everything the caller has liked, bookmarked or followed.\n\n#### Signature\n\n```http\nGET /stats/by-customer/{datatype}/{id} (datatype: string, id: string) -> The caller's signals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stats/by-resource/{datatype}/{id}`"}},"/studio-overview/{section}":{"get":{"operationId":"StudioOverviewController_getSection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"section","required":true,"in":"path","schema":{"type":"string","enum":["home","account","config","database","crm","store","finance","logistics","events","community","dam","build-studio","ai-automation"]},"description":"Which dashboard.","example":"home"},{"name":"site","in":"query","required":false,"description":"Narrows `home` and `build-studio` to one site (site name). Ignored by other sections.","schema":{"type":"string"},"example":"acme-shop"}],"responses":{"200":{"description":"{ section, generatedAt, ...figures }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"section":"home","generatedAt":"2026-09-29T14:00:00.000Z","customers":1240,"sites":2,"activityToday":37,"recentActivity":[]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Unknown overview section \"reports\" — `section` is not one of the listed dashboards.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Unknown overview section \"reports\"","path":"/studio-overview/{section}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"],"summary":"Get a Studio section overview","description":"The finished figures one Studio dashboard renders — counts, recent records and section-specific tiles — computed on the server so the screen only draws them. Every response carries `section` and `generatedAt` beside the section's own fields. Not cached.\n\n#### Signature\n\n```http\nGET /studio-overview/{section} (section: string, site?: string) -> { section, generatedAt, ...figures }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | UNKNOWN_SECTION | Unknown overview section \"reports\" | `section` is not one of the listed dashboards. | Use one of the enum values. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/k8/delete-service/{namespace}/{kind}/{name}":{"delete":{"operationId":"K8sManagementController_deleteService","summary":"Delete a Kubernetes resource","description":"Deletes a resource from the cluster. **Takes the workload down immediately** and cannot be undone — the manifest has to be re-applied to bring it back.\n\n#### Signature\n\n```http\nDELETE /k8/delete-service/{namespace}/{kind}/{name} (namespace: string, kind: string, name: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Immediately destructive.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /k8/apply-service`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"kind","required":true,"in":"path","description":"Resource kind.","schema":{"type":"string"},"example":"Deployment"},{"name":"namespace","required":true,"in":"path","description":"Namespace.","schema":{"type":"string"},"example":"org-4821"},{"name":"name","required":true,"in":"path","description":"Resource name.","schema":{"type":"string"},"example":"acme-shop"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Service deletion accepted"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/namespaces/{name}":{"delete":{"operationId":"K8sManagementController_deleteNamespace","summary":"Delete a namespace","description":"**Deletes an entire Kubernetes namespace and everything in it** — every deployment, service, secret and volume claim it holds.\n\nThis is the most destructive endpoint in the module. Namespaces typically correspond to a customer's workloads, so deleting one takes that customer offline entirely. There is no confirmation and no undo.\n\n#### Signature\n\n```http\nDELETE /k8/namespaces/{name} (name: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Destroys every resource in the namespace. No undo.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /k8/cleanup-resources`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Namespace to delete.","schema":{"type":"string"},"example":"org-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Namespace deletion accepted"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/namespaces/{namespaceName}/pods":{"get":{"operationId":"K8sManagementController_getPods","summary":"List pods in a namespace","description":"Pods running in a namespace, with their status — the first look when a site is not responding.\n\n#### Signature\n\n```http\nGET /k8/namespaces/{namespaceName}/pods (namespaceName: string) -> Pods\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /k8/check-status/{namespace}/{kind}/{resource}`","parameters":[{"name":"namespaceName","required":true,"in":"path","description":"Namespace.","schema":{"type":"string"},"example":"org-4821"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Pods","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/namespaces/{namespaceName}":{"get":{"operationId":"K8sManagementController_getNamespace","summary":"Get a namespace","description":"One namespace and its metadata.\n\n#### Signature\n\n```http\nGET /k8/namespaces/{namespaceName} (namespaceName: string) -> The namespace\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /k8/namespaces/{namespaceName}/pods`","parameters":[{"name":"namespaceName","required":true,"in":"path","description":"Namespace.","schema":{"type":"string"},"example":"org-4821"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The namespace","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/check-status/{namespace}/{kind}/{resource}":{"get":{"operationId":"K8sManagementController_checkStatus","summary":"Check a resource's status","description":"The status of one Kubernetes resource, by namespace, kind and name.\n\n#### Signature\n\n```http\nGET /k8/check-status/{namespace}/{kind}/{resource} (namespace: string, kind: string, resource: string) -> The status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /k8/apply-service`","parameters":[{"name":"namespace","required":true,"in":"path","description":"Namespace.","schema":{"type":"string"},"example":"org-4821"},{"name":"kind","required":true,"in":"path","description":"Resource kind.","schema":{"type":"string"},"example":"Deployment"},{"name":"resource","required":true,"in":"path","description":"Resource name.","schema":{"type":"string"},"example":"acme-shop"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/namespaces":{"get":{"operationId":"K8sManagementController_listNamespaces","summary":"List namespaces","description":"Kubernetes namespaces on the cluster.\n\n#### Signature\n\n```http\nGET /k8/namespaces () -> Namespaces\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /k8/namespaces/{namespaceName}`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Namespaces","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/apply-service":{"post":{"operationId":"K8sManagementController_applyService","summary":"Apply a Kubernetes service","description":"Applies a service manifest to the cluster. Applying is declarative: it creates or replaces, so a partial manifest can remove fields that were previously set.\n\n#### Signature\n\n```http\nPOST /k8/apply-service (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Declarative apply — omitted fields are dropped.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /k8/delete-service/{namespace}/{kind}/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The manifest.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"namespace":"org-4821","kind":"Deployment","name":"acme-shop","spec":{}}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/cleanup-resources":{"post":{"operationId":"K8sManagementController_cleanupSiteResources","summary":"Clean up site resources","description":"Removes the cluster resources belonging to a site. Destructive by design — it exists to reclaim what a deleted site left behind, so confirm the site really is gone first.\n\n#### Signature\n\n```http\nPOST /k8/cleanup-resources (body) -> What was removed\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Deletes cluster resources.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /k8/delete-service/{namespace}/{kind}/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Which site.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","namespace":"org-4821"}}}},"responses":{"200":{"description":"Site resources cleanup initiated","content":{"application/json":{"schema":{"type":"object","properties":{"siteName":{"type":"string","description":"Site name"},"siteOrgId":{"type":"string","description":"Site organization ID"},"k8sCleanup":{"type":"object","description":"K8s resource cleanup results"},"nginxCleanup":{"type":"object","description":"Nginx configuration cleanup results"},"message":{"type":"string","description":"Cleanup status message"}}}}}},"201":{"description":"What was removed","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/scale-up":{"post":{"operationId":"K8sManagementController_scaleUp","summary":"Scale up a deployment","description":"Raises a deployment's replica count — bringing a scaled-to-zero workload back, or adding capacity. Costs cluster resources.\n\n#### Signature\n\n```http\nPOST /k8/scale-up (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /k8/scale-down`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to scale.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"namespace":"org-4821","name":"acme-shop","replicas":2}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/k8/scale-down":{"post":{"operationId":"K8sManagementController_scaleToZero","summary":"Scale a deployment to zero","description":"Scales a deployment down to zero replicas. The workload stops serving entirely — this is how an idle site is parked, and it is indistinguishable from an outage to anyone visiting it.\n\n#### Signature\n\n```http\nPOST /k8/scale-down (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Takes the workload offline.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /k8/scale-up`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to scale down.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"namespace":"org-4821","name":"acme-shop"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Kubernetes"]}},"/org-management/check-org-name/{orgName}":{"get":{"operationId":"OrgManagementController_checkOrgNameAvailability","summary":"Check organization name availability","description":"Whether a name is free to register. **Public**, because it runs during signup before an account exists — which also means it lets anyone enumerate which org names are taken.\n\n#### Signature\n\n```http\nGET /org-management/check-org-name/{orgName} (orgName: string) -> Availability\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated; discloses whether a name is taken.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /org-management/org-register`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orgName","required":true,"in":"path","schema":{"type":"string"},"description":"The name to check.","example":"acme"}],"requestBody":{"required":true,"description":"Organization name to check","content":{"application/json":{"schema":{"type":"object","properties":{"orgName":{"type":"string","description":"Organization name to check"}},"required":["orgName"]}}}},"responses":{"200":{"description":"Availability","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"available":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/profile/{orgId}":{"get":{"operationId":"OrgManagementController_getProfile","summary":"Get an organization profile","description":"The full profile — company, customer and address information. `orgId` is optional: omit it for the calling org, supply it to read another org (which the caller must be entitled to do).\n\n#### Signature\n\n```http\nGET /org-management/profile/{orgId} (orgId: string) -> The profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/profile/update`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orgId","required":true,"in":"path","schema":{"type":"string"},"description":"Org to read. Defaults to the caller's own.","example":"org_4821"}],"responses":{"200":{"description":"The profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/profile/{orgId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/profile/update":{"post":{"operationId":"OrgManagementController_updateCompanyProfile","summary":"Update the company profile","description":"Updates company profile information on the root org record.\n\n#### Signature\n\n```http\nPOST /org-management/profile/update (body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /org-management/profile/customer`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"companyName":"Acme Ltd","phone":"+15551234567"}}}},"responses":{"201":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/company/meta":{"post":{"operationId":"OrgManagementController_updateCompanyMeta","summary":"Update company metadata","description":"Merges a namespaced blob into the **caller's own** org at `company.data.meta` — for example `{ namespace: \"onboarding\", value: { setupCompletedAt } }`.\n\nDeliberately narrow: it writes only `data.meta.<namespace>.*`. Plan, balance and status cannot be reached through it, which is what makes it safe to expose to an app that needs to remember its own state.\n\n#### Signature\n\n```http\nPOST /org-management/company/meta (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Cannot reach plan, balance or status.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAMESPACE_REQUIRED | A valid meta namespace is required | `namespace` is missing or invalid. | Supply a namespace. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/profile/{orgId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The namespace and its value.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["namespace","value"],"properties":{"namespace":{"type":"string","example":"onboarding"},"value":{"type":"object","description":"Must be an object.","additionalProperties":true,"example":{"setupCompletedAt":"2026-08-30T10:00:00.000Z"}}}},"example":{"namespace":"onboarding","value":{"setupCompletedAt":"2026-08-30T10:00:00.000Z"}}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid meta namespace is required — `namespace` is missing or invalid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid meta namespace is required","path":"/org-management/company/meta","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/profile/customer":{"put":{"operationId":"OrgManagementController_updateCustomerProfile","summary":"Update the customer profile","description":"Updates the customer-side profile on the root org record.\n\n#### Signature\n\n```http\nPUT /org-management/profile/customer (body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/profile/update`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"firstName":"Ada","lastName":"Lovelace"}}}},"responses":{"200":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/profile/addresses":{"get":{"operationId":"OrgManagementController_getAddresses","summary":"Get addresses","description":"The current user's addresses.\n\n#### Signature\n\n```http\nGET /org-management/profile/addresses () -> Addresses\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/profile/addresses`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Addresses","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]},"post":{"operationId":"OrgManagementController_addAddress","summary":"Add an address","description":"Adds an address for the current user.\n\n#### Signature\n\n```http\nPOST /org-management/profile/addresses (body) -> The address\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/profile/addresses`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The address.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"line1":"1 Market St","city":"San Francisco","state":"CA","postalCode":"94105","country":"US"}}}},"responses":{"201":{"description":"The address","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/billing/balance":{"get":{"operationId":"OrgManagementController_getBalance","summary":"Get the credit balance","description":"Current credit balance and spending. `customerOrgId` reads another org's balance, for an operator managing a customer.\n\n#### Signature\n\n```http\nGET /org-management/billing/balance (customerOrgId?: string) -> Balance and spending\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/billing/transactions`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"customerOrgId","required":false,"in":"query","description":"Read another org's balance.","schema":{"type":"string"},"example":"org_4821"}],"responses":{"200":{"description":"Balance and spending","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/billing/buy-credits":{"post":{"operationId":"OrgManagementController_buyCredits","summary":"Buy credits","description":"Starts a credit purchase through the payment gateway. **Charges a real card.** Marked public because it is also reachable from a payment-return context; supply the org explicitly when there is no session.\n\n#### Signature\n\n```http\nPOST /org-management/billing/buy-credits (body) -> The payment setup\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Charges a card.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_AMOUNT | Invalid amount | The amount is missing or not positive. | Send a positive amount. |\n| `402` | PAYMENT_FAILED | Payment failed | The card was declined or the charge could not be taken. | Try another payment method. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /org-management/billing/complete-payment`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The purchase.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","example":5000},"quantity":{"type":"integer","example":1},"customerOrgId":{"type":"string","example":"org_4821"}}},"example":{"amount":5000}}}},"responses":{"201":{"description":"The payment setup","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid amount — The amount is missing or not positive.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid amount","path":"/org-management/billing/buy-credits","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"402":{"description":"Payment failed — The card was declined or the charge could not be taken.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"Payment failed","path":"/org-management/billing/buy-credits","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/billing/complete-payment":{"post":{"operationId":"OrgManagementController_completePayment","summary":"Complete a credit payment","description":"Finalises a pending credit purchase after Stripe validation, crediting the balance. The session id is single-use — replaying one is refused rather than crediting twice.\n\n#### Signature\n\n```http\nPOST /org-management/billing/complete-payment (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Single-use per session.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PAYMENT_INTENT_REQUIRED | Payment intent ID is required | No intent id was supplied. | Pass the intent id from the client-side confirmation. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /org-management/billing/buy-credits`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The payment reference.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentIntentId":{"type":"string","example":"pi_3Abc123"},"sessionId":{"type":"string","example":"cs_test_a1b2c3"}}},"example":{"paymentIntentId":"pi_3Abc123"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Payment intent ID is required — No intent id was supplied.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Payment intent ID is required","path":"/org-management/billing/complete-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/billing/transactions":{"get":{"operationId":"OrgManagementController_getTransactionHistory","summary":"Get transaction history","description":"The org's billing transactions.\n\n#### Signature\n\n```http\nGET /org-management/billing/transactions () -> Transactions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/billing/balance`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"customerOrgId","required":false,"in":"query","description":"Read another org (root only). Defaults to the caller.","schema":{}},{"name":"endDate","required":false,"in":"query","description":"End date filter","schema":{"type":"string"}},{"name":"startDate","required":false,"in":"query","description":"Start date filter","schema":{"type":"string"}},{"name":"pageSize","required":false,"in":"query","description":"Items per page","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","description":"Page number","schema":{"type":"number"}}],"responses":{"200":{"description":"Transactions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/billing/transactions/backfill-org-links":{"post":{"operationId":"OrgManagementController_backfillTransactionOrgLinks","summary":"Backfill transaction org links","description":"One-time repair: links root-owned credit purchases that predate the `data.customerOrgId` field back to the org they were paid for, using their Stripe PaymentIntent.\n\nIdempotent, so a re-run is safe, but it rewrites historical ledger rows — run it deliberately and bound it with `limit` on a first pass.\n\n#### Signature\n\n```http\nPOST /org-management/billing/transactions/backfill-org-links (limit?: integer) -> What was linked\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Notes\n\n- Rewrites historical transactions. Idempotent.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/billing/transactions`","parameters":[{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Cap how many rows are processed.","example":100},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"What was linked","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/billing/gift-credits":{"post":{"operationId":"OrgManagementController_giftCredits","summary":"Gift credits (admin)","description":"Grants credits to an org without payment. Admin-only, and it creates spendable balance out of nothing — the transaction record is the only trail, so record why.\n\n#### Signature\n\n```http\nPOST /org-management/billing/gift-credits (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Creates balance with no payment behind it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_AMOUNT | Invalid amount | The amount is not positive. | Send a positive amount. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/billing/add-credit`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The gift.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerOrgId":{"type":"string","example":"org_4821"},"amount":{"type":"number","example":5000},"reason":{"type":"string","example":"Service credit for the March outage"}}},"example":{"customerOrgId":"org_4821","amount":5000,"reason":"Service credit for the March outage"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid amount — The amount is not positive.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid amount","path":"/org-management/billing/gift-credits","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/billing/add-credit":{"post":{"operationId":"OrgManagementController_addCredit","summary":"Add credit to an organization","description":"Adds credit directly to an org and writes the transaction record. An email is required so the record says **who** the credit was added for — a credit with no attributable person cannot be audited later.\n\n#### Signature\n\n```http\nPOST /org-management/billing/add-credit (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_REQUIRED | An email is required to record who the credit was added for | `email` is missing. | Supply the recipient email. |\n| `422` | INVALID_CUSTOMER_ORG | Invalid customerOrgId <id> | The target org does not exist. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/billing/gift-credits`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An email is required to record who the credit was added for — `email` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An email is required to record who the credit was added for","path":"/org-management/billing/add-credit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Invalid customerOrgId <id> — The target org does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Invalid customerOrgId <id>","path":"/org-management/billing/add-credit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"The credit.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"customerOrgId":{"type":"string","example":"org_4821"},"amount":{"type":"number","example":5000},"email":{"type":"string","description":"Who the credit was added for.","example":"ada@example.com"},"note":{"type":"string"}}},"example":{"customerOrgId":"org_4821","amount":5000,"email":"ada@example.com"}}}}}},"/org-management/subscription/plans":{"get":{"operationId":"OrgManagementController_getSubscriptionPlans","summary":"Get subscription plans","description":"The plans available. Public, since pricing pages show it before signup. `customer-org-id` (a **hyphenated** query parameter) scopes the list to what a specific org can take.\n\n#### Signature\n\n```http\nGET /org-management/subscription/plans (customer-org-id?: string) -> Plans\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /org-management/subscription/current`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"customer-org-id","required":false,"in":"query","schema":{"type":"string"},"description":"Note the hyphens — this is not `customerOrgId`.","example":"org_4821"}],"responses":{"200":{"description":"Plans","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/subscription/current":{"get":{"operationId":"OrgManagementController_getCurrentSubscription","summary":"Get the current subscription","description":"The org's current plan, status and expiry. `customer-org-id` reads another org's.\n\n#### Signature\n\n```http\nGET /org-management/subscription/current (customer-org-id?: string) -> The subscription\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `500` | PLAN_RETRIEVAL_FAILED | Failed to retrieve customer plan | The plan record could not be read. | Retry. |\n\nPlus the standard platform errors: `429`.\n\n#### See also\n\n- `POST /org-management/subscription/create`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"customer-org-id","required":false,"in":"query","schema":{"type":"string"},"example":"org_4821"}],"responses":{"200":{"description":"The subscription","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to retrieve customer plan — The plan record could not be read.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to retrieve customer plan","path":"/org-management/subscription/current","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Organization"]}},"/org-management/subscription/create":{"post":{"operationId":"OrgManagementController_createSubscription","summary":"Create a subscription","description":"Starts a new subscription. **Charges the card** and sets the org's plan, which changes what the account is allowed to do.\n\n#### Signature\n\n```http\nPOST /org-management/subscription/create (body) -> The subscription\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Charges a card and changes plan entitlements.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLAN_REQUIRED | Plan is required | `plan` is missing. | Name a plan. |\n| `402` | PAYMENT_FAILED | Payment failed | The card was declined or the charge could not be taken. | Try another payment method. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /org-management/subscription/upgrade`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The plan.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","example":"pro-monthly"},"customerOrgId":{"type":"string","example":"org_4821"},"paymentMethodId":{"type":"string","example":"pm_1Abc123"}}},"example":{"plan":"pro-monthly","paymentMethodId":"pm_1Abc123"}}}},"responses":{"201":{"description":"The subscription","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Plan is required — `plan` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Plan is required","path":"/org-management/subscription/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"402":{"description":"Payment failed — The card was declined or the charge could not be taken.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"Payment failed","path":"/org-management/subscription/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/subscription/upgrade":{"post":{"operationId":"OrgManagementController_upgradeSubscription","summary":"Upgrade a subscription","description":"Moves the org to a higher plan. Charges the difference and raises the entitlements immediately.\n\n#### Signature\n\n```http\nPOST /org-management/subscription/upgrade (body) -> The subscription\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Charges the difference.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NEW_PLAN_REQUIRED | New plan is required | `newPlan` is missing. | Name the target plan. |\n| `402` | PAYMENT_FAILED | Payment failed | The card was declined or the charge could not be taken. | Try another payment method. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /org-management/subscription/downgrade`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The target plan.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["newPlan"],"properties":{"newPlan":{"type":"string","example":"enterprise-monthly"},"customerOrgId":{"type":"string"}}},"example":{"newPlan":"enterprise-monthly"}}}},"responses":{"201":{"description":"The subscription","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"New plan is required — `newPlan` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"New plan is required","path":"/org-management/subscription/upgrade","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"402":{"description":"Payment failed — The card was declined or the charge could not be taken.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"Payment failed","path":"/org-management/subscription/upgrade","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/subscription/downgrade":{"post":{"operationId":"OrgManagementController_downgradeSubscription","summary":"Downgrade a subscription","description":"Moves the org to a lower plan. **Lowers entitlements**, so anything the org has that exceeds the new plan's limits — sites, seats, storage — may stop working or be refused on next use. Check usage against the target plan first.\n\n#### Signature\n\n```http\nPOST /org-management/subscription/downgrade (body) -> The subscription\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Reduces what the org is allowed to do.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NEW_PLAN_REQUIRED | New plan is required | `newPlan` is missing. | Name the target plan. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/subscription/upgrade`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The target plan.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["newPlan"],"properties":{"newPlan":{"type":"string","example":"starter-monthly"},"customerOrgId":{"type":"string"}}},"example":{"newPlan":"starter-monthly"}}}},"responses":{"201":{"description":"The subscription","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"New plan is required — `newPlan` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"New plan is required","path":"/org-management/subscription/downgrade","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/subscription/cancel":{"post":{"operationId":"OrgManagementController_cancelSubscription","summary":"Cancel a subscription","description":"Cancels the current subscription. The org keeps its data but loses plan entitlements at the end of the term — separate from cancelling the account itself.\n\n#### Signature\n\n```http\nPOST /org-management/subscription/cancel (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/account/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"No longer needed"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/subscription/admin/update":{"post":{"operationId":"OrgManagementController_updateCustomerPlan","summary":"Update a customer plan (admin)","description":"Admin override for a customer's plan — type, status and expiry — with audit logging. Sets the plan directly without payment, so it is the tool for a negotiated arrangement, not the normal upgrade path.\n\n#### Signature\n\n```http\nPOST /org-management/subscription/admin/update (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Bypasses payment. Audited.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/subscription/current`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/subscription/admin/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"The plan fields to set.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"customerOrgId":"org_4821","planType":"enterprise","status":"active","expiry":"2027-08-30"}}}}}},"/org-management/subscription/complete-payment":{"post":{"operationId":"OrgManagementController_completeSubscriptionPayment","summary":"Complete a subscription payment","description":"Finalises a pending subscription payment after Stripe validation and activates the plan.\n\n#### Signature\n\n```http\nPOST /org-management/subscription/complete-payment (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PAYMENT_INTENT_REQUIRED | Payment intent ID is required | No intent id was supplied. | Pass the intent id from the client-side confirmation. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /org-management/subscription/complete-checkout`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The payment reference.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentIntentId":{"type":"string","example":"pi_3Abc123"}}},"example":{"paymentIntentId":"pi_3Abc123"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Payment intent ID is required — No intent id was supplied.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Payment intent ID is required","path":"/org-management/subscription/complete-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/subscription/complete-checkout":{"post":{"operationId":"OrgManagementController_completeSubscriptionCheckout","summary":"Complete a subscription checkout","description":"Activates a subscription after a successful Stripe Checkout session. The session id is single-use — a replay is refused rather than starting a second subscription.\n\n#### Signature\n\n```http\nPOST /org-management/subscription/complete-checkout (body) -> The subscription\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SESSION_REQUIRED | Session ID is required | `sessionId` is missing. | Pass the Stripe session id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/subscription/complete-payment`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The checkout session.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sessionId"],"properties":{"sessionId":{"type":"string","example":"cs_test_a1b2c3"}}},"example":{"sessionId":"cs_test_a1b2c3"}}}},"responses":{"201":{"description":"The subscription","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Session ID is required — `sessionId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Session ID is required","path":"/org-management/subscription/complete-checkout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/account/cancel":{"post":{"operationId":"OrgManagementController_cancelAccount","summary":"Cancel the account","description":"Cancels the entire organization account — not just the subscription. Everything the org runs stops. Reactivation is an admin action, so this is not something a customer can undo themselves.\n\n#### Signature\n\n```http\nPOST /org-management/account/cancel (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Takes the whole org offline; only an admin can reverse it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_MISMATCH | Email confirmation does not match | The confirmation email does not match the org's. | Type the account email exactly. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /org-management/account/reactivate`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Confirmation and reason.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"confirmEmail":"owner@acme.example","reason":"Closing the business"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Email confirmation does not match — The confirmation email does not match the org's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email confirmation does not match","path":"/org-management/account/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/account/reactivate":{"put":{"operationId":"OrgManagementController_reactivateAccount","summary":"Reactivate an account (admin)","description":"Brings a cancelled account back. Admin only; refused if the account is already active.\n\n#### Signature\n\n```http\nPUT /org-management/account/reactivate () -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ALREADY_ACTIVE | Account is already active | The account was not cancelled. | Nothing to do. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/account/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Account is already active — The account was not cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Account is already active","path":"/org-management/account/reactivate","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/pre-approved-signups":{"post":{"operationId":"OrgManagementController_addPreApprovedSignup","summary":"Add a pre-approved signup (admin)","description":"Adds an email or a signup code to the pre-approved list. A code can be redeemed by anyone who has it, so treat codes as shareable and emails as individual.\n\n#### Signature\n\n```http\nPOST /org-management/pre-approved-signups (body) -> The entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- A code is usable by anyone holding it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_EMAIL_FORMAT | Invalid email format | The email is malformed. | Supply a valid address. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/pre-approved-signups/remove`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The entry.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"ada@example.com"},"code":{"type":"string","example":"PARTNER-2026"}}},"example":{"email":"ada@example.com"}}}},"responses":{"201":{"description":"The entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid email format — The email is malformed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid email format","path":"/org-management/pre-approved-signups","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]},"get":{"operationId":"OrgManagementController_getPreApprovedSignups","summary":"List pre-approved signups (admin)","description":"The emails and signup codes cleared to register.\n\n#### Signature\n\n```http\nGET /org-management/pre-approved-signups (type?: string, active?: boolean) -> Pre-approved entries\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/pre-approved-signups`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"type","required":false,"in":"query","description":"`email` or `code`.","schema":{"type":"string"},"example":"email"},{"name":"active","required":false,"in":"query","schema":{"type":"boolean"},"example":true}],"responses":{"200":{"description":"Pre-approved entries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/pre-approved-signups/remove":{"post":{"operationId":"OrgManagementController_removePreApprovedSignup","summary":"Remove a pre-approved signup (admin)","description":"Removes an email or code from the pre-approved list, so it can no longer be used to register.\n\n#### Signature\n\n```http\nPOST /org-management/pre-approved-signups/remove (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/pre-approved-signups`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The entry to remove.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/pre-approved-signups/validate":{"post":{"operationId":"OrgManagementController_validatePreApprovedSignup","summary":"Validate a pre-approved signup","description":"Checks whether an email or code is cleared to sign up. **Public**, because it runs before an account exists — which also makes it an oracle for whether a given email is on the list, so rate-limit it.\n\n#### Signature\n\n```http\nPOST /org-management/pre-approved-signups/validate (body) -> Whether it is approved\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated; confirms list membership.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /org-management/pre-approved-signups`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to check.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}},"responses":{"201":{"description":"Whether it is approved","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"approved":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/org-register":{"post":{"operationId":"OrgManagementController_registerOrganization","summary":"Register an organization","description":"Creates an organization and, optionally, a development environment for cloud development. Provisioning a site or dev environment can fail after the org is created, so check the response rather than assuming all parts exist.\n\n#### Signature\n\n```http\nPOST /org-management/org-register (body) -> The organization\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `System`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Partial success is possible.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | INVALID_EMAIL | Invalid Email <email> | The owner email is malformed. | Supply a valid address. |\n| `500` | PROVISIONING_FAILED | Failed to create site or dev environment | The org was created but provisioning failed. | The org exists — retry the provisioning step rather than re-registering. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /org-management/check-org-name/{orgName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The organization to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"orgName":"acme","ownerEmail":"owner@acme.example","createDevEnv":true}}}},"responses":{"201":{"description":"The organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Invalid Email <email> — The owner email is malformed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Invalid Email <email>","path":"/org-management/org-register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to create site or dev environment — The org was created but provisioning failed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to create site or dev environment","path":"/org-management/org-register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Organization"]}},"/org-management/business-made-register":{"post":{"operationId":"OrgManagementController_registerBusinessMadeOrganization","summary":"Register a Business Made organization","description":"Public org creation dedicated to the Business Made app. **Origin/Referer must match** localhost, appmint.io or businessmade.io — that check is the only thing standing between this route and open org creation, since it is otherwise unauthenticated.\n\nIt forces the plan into the `bm-*` family and does **not** provision a site or dev environment, unlike `org-register`.\n\n#### Signature\n\n```http\nPOST /org-management/business-made-register (body) -> The organization\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated; gated only by Origin/Referer.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | ORIGIN_NOT_ALLOWED | Origin not allowed | The Origin or Referer is not one of the permitted hosts. | Call it from an allowed front end. |\n| `422` | INVALID_EMAIL | Invalid Email <email> | The owner email is malformed. | Supply a valid address. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /org-management/org-register`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The organization.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"orgName":"acme-hr","ownerEmail":"owner@acme.example"}}}},"responses":{"201":{"description":"The organization","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Origin not allowed — The Origin or Referer is not one of the permitted hosts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Origin not allowed","path":"/org-management/business-made-register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"Invalid Email <email> — The owner email is malformed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Invalid Email <email>","path":"/org-management/business-made-register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/delete":{"post":{"operationId":"OrgManagementController_deleteOrganization","summary":"Delete an organization","description":"Deletes an organization and, optionally, its sites and resources. Destructive and not recoverable through this API — everything the org owns goes with it. Cancel the account instead if the data may be needed.\n\n#### Signature\n\n```http\nPOST /org-management/delete (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `System`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Irreversible. Prefer account cancellation.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/account/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to delete.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"orgId":{"type":"string","example":"org_4821"},"deleteSites":{"type":"boolean","example":true},"deleteResources":{"type":"boolean","example":true}}},"example":{"orgId":"org_4821","deleteSites":true}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/delete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/services/available":{"get":{"operationId":"OrgManagementController_getAvailableServices","summary":"Get available services","description":"Additional services the org can buy — export time, support and the like.\n\n#### Signature\n\n```http\nGET /org-management/services/available (category?: string) -> Available services\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/services/purchase`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"category","required":false,"in":"query","schema":{"type":"string"},"example":"support"}],"responses":{"200":{"description":"Available services","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/services/purchase":{"post":{"operationId":"OrgManagementController_purchaseService","summary":"Purchase a service","description":"Buys an additional service. Charges the card or draws down credit, depending on the service.\n\n#### Signature\n\n```http\nPOST /org-management/services/purchase (body) -> The purchase\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Costs money.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SERVICE_ID_REQUIRED | Service ID is required | `serviceId` is missing. | Name the service. |\n| `402` | PAYMENT_FAILED | Payment failed | The card was declined or the charge could not be taken. | Try another payment method. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/services/complete-payment`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to buy.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["serviceId"],"properties":{"serviceId":{"type":"string","example":"export-time"},"quantity":{"type":"integer","example":1}}},"example":{"serviceId":"export-time","quantity":1}}}},"responses":{"201":{"description":"The purchase","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Service ID is required — `serviceId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Service ID is required","path":"/org-management/services/purchase","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"description":"Payment failed — The card was declined or the charge could not be taken.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"Payment failed","path":"/org-management/services/purchase","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/services/purchased":{"get":{"operationId":"OrgManagementController_getPurchasedServices","summary":"Get purchased services","description":"Services the org has bought and their status.\n\n#### Signature\n\n```http\nGET /org-management/services/purchased (status?: string) -> Purchased services\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/services/available`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"}],"responses":{"200":{"description":"Purchased services","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/services/complete-payment":{"post":{"operationId":"OrgManagementController_completeServicePayment","summary":"Complete a service payment","description":"Finalises a pending service payment after gateway validation and activates the service.\n\n#### Signature\n\n```http\nPOST /org-management/services/complete-payment (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PAYMENT_INTENT_REQUIRED | Payment intent ID is required | No intent id was supplied. | Pass the intent id from the client-side confirmation. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/services/purchase`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The payment reference.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"paymentIntentId":"pi_3Abc123"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Payment intent ID is required — No intent id was supplied.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Payment intent ID is required","path":"/org-management/services/complete-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/transfer/ownership":{"post":{"operationId":"OrgManagementController_transferOrgOwnership","summary":"Transfer organization ownership","description":"Hands an organization to a new email, creating a user account for the new owner with the admin role.\n\nThis gives someone else administrative control of the org. There is no confirmation step from the recipient — the transfer takes effect on the call, so verify the address character by character.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/ownership (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Notes\n\n- Grants admin control immediately, with no recipient confirmation.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_NEW_OWNER_EMAIL | Invalid email format for new owner | The address is malformed. | Check the address. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/transfer/change-email`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid email format for new owner — The address is malformed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid email format for new owner","path":"/org-management/transfer/ownership","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"The new owner.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["newOwnerEmail"],"properties":{"newOwnerEmail":{"type":"string","example":"newowner@acme.example"}}},"example":{"newOwnerEmail":"newowner@acme.example"}}}}}},"/org-management/transfer/change-email":{"post":{"operationId":"OrgManagementController_changeOrgPrimaryEmail","summary":"Change the primary email","description":"Changes an org's primary email **without** transferring ownership — for a rename or a mailbox change, where `transfer/ownership` would be wrong.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/change-email (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_EMAIL_FORMAT | Invalid email format | The address is malformed. | Check the address. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/transfer/ownership`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid email format — The address is malformed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid email format","path":"/org-management/transfer/change-email","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"The new email.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["newEmail"],"properties":{"newEmail":{"type":"string","example":"billing@acme.example"}}},"example":{"newEmail":"billing@acme.example"}}}}}},"/org-management/transfer/assets":{"post":{"operationId":"OrgManagementController_transferAssets","summary":"Transfer assets between organizations","description":"Moves or copies assets from one org to another. **`move` deletes the original** — copy first if there is any doubt, since a move across orgs is not undone by transferring back.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/assets (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- `move` is destructive on the source.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SAME_ORG | Source and target organizations cannot be the same | Source and target match. | Pick two different orgs. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/transfer/assets-bulk`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Source and target organizations cannot be the same — Source and target match.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Source and target organizations cannot be the same","path":"/org-management/transfer/assets","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"What to transfer, and how.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sourceOrgId":{"type":"string","example":"org_4821"},"targetOrgId":{"type":"string","example":"org_7712"},"assetType":{"type":"string","example":"product"},"ids":{"type":"array","items":{"type":"string"}},"mode":{"type":"string","enum":["copy","move"],"description":"`move` deletes the original.","example":"copy"}}},"example":{"sourceOrgId":"org_4821","targetOrgId":"org_7712","assetType":"product","mode":"copy"}}}}}},"/org-management/transfer/assets-bulk":{"post":{"operationId":"OrgManagementController_transferAssetsBulk","summary":"Bulk-transfer assets between organizations","description":"Transfers several asset types at once. Same `copy`/`move` semantics as the single form, applied across everything named — which makes a `move` here considerably wider in blast radius.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/assets-bulk (body) -> Per-type results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- A bulk `move` deletes across every named type.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SAME_ORG | Source and target organizations cannot be the same | Source and target match. | Pick two different orgs. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/transfer/assets`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Per-type results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Source and target organizations cannot be the same — Source and target match.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Source and target organizations cannot be the same","path":"/org-management/transfer/assets-bulk","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"What to transfer.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sourceOrgId":{"type":"string","example":"org_4821"},"targetOrgId":{"type":"string","example":"org_7712"},"assetTypes":{"type":"array","items":{"type":"string"},"example":["product","customer"]},"mode":{"type":"string","enum":["copy","move"],"example":"copy"}}},"example":{"sourceOrgId":"org_4821","targetOrgId":"org_7712","assetTypes":["product","customer"],"mode":"copy"}}}}}},"/org-management/services/pricing/{serviceName}":{"get":{"operationId":"OrgManagementController_getServicePricing","summary":"Get service pricing","description":"Pricing and terms for one service.\n\n#### Signature\n\n```http\nGET /org-management/services/pricing/{serviceName} (serviceName: string) -> Pricing and terms\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/services/available`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"serviceName","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"export-time"}],"responses":{"200":{"description":"Pricing and terms","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/services/agreements":{"get":{"operationId":"OrgManagementController_getServiceAgreements","summary":"Get service agreements","description":"Every service agreement the org has, and its state.\n\n#### Signature\n\n```http\nGET /org-management/services/agreements () -> Agreements\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/services/agreement/{serviceName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Agreements","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/init/{orgid}":{"get":{"operationId":"OrgManagementController_initStatus","summary":"Check an org's initialization","description":"Per step, whether the org has what every org starts with: the access-request workflow, the `meeting` reservation definition, and the publishing-approval workflow (seeded switched off). `complete` is true when every step is present.\n\n#### Signature\n\n```http\nGET /org-management/init/{orgid} (orgid: string) -> { orgId, steps: [{ key, title, present }], complete }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootSystem`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/init/{orgid}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orgid","in":"path","required":true,"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","schema":{"type":"string"},"example":"org_4821"}],"responses":{"200":{"description":"{ orgId, steps: [{ key, title, present }], complete }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"orgId":"acme","complete":false,"steps":[{"key":"workflow.access-approval","title":"Access request workflow","present":true},{"key":"reservation-definition.meeting","title":"Meeting bookings (reservation definition \"meeting\")","present":false}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]},"post":{"operationId":"OrgManagementController_initRun","summary":"Initialize an org","parameters":[{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"{ orgId, done, present, failed }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"orgId":"acme","done":["reservation-definition.meeting"],"present":["workflow.access-approval","workflow.publish-approval"],"failed":[]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"description":"Runs every initialization step the org is missing and leaves the rest alone — safe to repeat. A step that fails is reported and the others still run.\n\n#### Signature\n\n```http\nPOST /org-management/init/{orgid} (orgid: string) -> { orgId, done, present, failed }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootSystem`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/init/{orgid}`"}},"/org-management/setup/catalog":{"get":{"operationId":"OrgManagementController_getSetupCatalog","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The catalog with status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Get the setup wizard catalog","description":"Every configuration item in the setup wizard, with live per-item status and completeness for this org — one call behind a whole onboarding checklist.\n\n#### Signature\n\n```http\nGET /org-management/setup/catalog () -> The catalog with status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/company/meta`"}},"/org-management/services/agreement/{serviceName}":{"get":{"operationId":"OrgManagementController_getServiceAgreement","summary":"Get a service agreement","description":"Whether the org has accepted the terms for one shareable service.\n\n#### Signature\n\n```http\nGET /org-management/services/agreement/{serviceName} (serviceName: string) -> The agreement status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/services/agreement/accept/{serviceName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"serviceName","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"export-time"}],"responses":{"200":{"description":"The agreement status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/services/agreement/accept/{serviceName}":{"post":{"operationId":"OrgManagementController_acceptServiceAgreement","summary":"Accept a service agreement","description":"Records acceptance of the terms for a shareable service — a legal act on the org's behalf, recorded against the accepting user.\n\n#### Signature\n\n```http\nPOST /org-management/services/agreement/accept/{serviceName} (serviceName: string, body) -> The agreement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Records a binding acceptance.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/services/agreement/revoke/{serviceName}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"serviceName","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"export-time"}],"responses":{"201":{"description":"The agreement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"Acceptance detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"version":"2026-01"}}}}}},"/org-management/services/agreement/revoke/{serviceName}":{"post":{"operationId":"OrgManagementController_revokeServiceAgreement","summary":"Revoke a service agreement","description":"Withdraws acceptance for a shareable service. Anything relying on that agreement stops working.\n\n#### Signature\n\n```http\nPOST /org-management/services/agreement/revoke/{serviceName} (serviceName: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/services/agreements`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"serviceName","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"export-time"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/users/{orgid}":{"get":{"operationId":"OrgManagementController_getOrgUsers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"200":{"description":"Users","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"List an organization's users","description":"Users in the named org. The path `orgid` is the org being read; the header identifies the caller, which is what lets an operator read another org's users.\n\n#### Signature\n\n```http\nGET /org-management/users/{orgid} (orgid: string) -> Users\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/users/{orgid}/{userId}`"}},"/org-management/users/{orgid}/{userId}":{"get":{"operationId":"OrgManagementController_getOrgUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"User id.","example":"USR-4821"}],"responses":{"200":{"description":"The user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}/{userId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Get an organization user","description":"One user in the named org.\n\n#### Signature\n\n```http\nGET /org-management/users/{orgid}/{userId} (orgid: string, userId: string) -> The user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/users/{orgid}/update`"}},"/org-management/users/{orgid}/create":{"post":{"operationId":"OrgManagementController_createOrgUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Create an organization user","description":"Creates a user inside the named org. The role given here decides what they can do — a user created with an admin role has administrative control from the moment they sign in.\n\n#### Signature\n\n```http\nPOST /org-management/users/{orgid}/create (orgid: string, body) -> The user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- The role grants real access.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/users/{orgid}/update`","requestBody":{"description":"The user.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@acme.example","firstName":"Ada","lastName":"Lovelace","role":"editor"}}}}}},"/org-management/users/{orgid}/update":{"post":{"operationId":"OrgManagementController_updateOrgUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Update an organization user","description":"Updates a user in the named org, including their role. A role change takes effect on their next request.\n\n#### Signature\n\n```http\nPOST /org-management/users/{orgid}/update (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/users/{orgid}/status`","requestBody":{"description":"The user id and fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"USR-4821","role":"admin"}}}}}},"/org-management/users/{orgid}/status":{"post":{"operationId":"OrgManagementController_setOrgUsersStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Set organization users' status","description":"Activates or deactivates users in bulk. Deactivating removes their access at once — the reversible alternative to deleting them.\n\n#### Signature\n\n```http\nPOST /org-management/users/{orgid}/status (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/users/{orgid}/delete`","requestBody":{"description":"Which users, and the status.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"example":["USR-4821"]},"status":{"type":"string","example":"inactive"}}},"example":{"ids":["USR-4821"],"status":"inactive"}}}}}},"/org-management/users/{orgid}/delete":{"post":{"operationId":"OrgManagementController_deleteOrgUsers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}/delete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Delete organization users","description":"Permanently removes users from the named org. Deactivate instead unless the records genuinely should not exist — deletion loses the audit association with anything they did.\n\n#### Signature\n\n```http\nPOST /org-management/users/{orgid}/delete (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Irreversible; prefer deactivation.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/users/{orgid}/status`","requestBody":{"description":"Which users.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"example":["USR-4821"]}}},"example":{"ids":["USR-4821"]}}}}}},"/org-management/users/{orgid}/change-password":{"post":{"operationId":"OrgManagementController_resetOrgUserPassword","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}/change-password","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Change an organization user's password","description":"Sets a user's password directly, without their involvement. An administrative override — the user is not asked to confirm, and any session they hold may be invalidated. Prefer sending a reset link.\n\n#### Signature\n\n```http\nPOST /org-management/users/{orgid}/change-password (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Sensitive payload; prefer `send-reset`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/users/{orgid}/send-reset`","requestBody":{"description":"The user and the new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"USR-4821","password":"<new password>"}}}}}},"/org-management/users/{orgid}/send-reset":{"post":{"operationId":"OrgManagementController_sendOrgUserReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/users/{orgid}/send-reset","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Send a password reset to an organization user","description":"Emails a reset link to the user, letting them set their own password. The preferred route — it sends a real email, so check the recipient.\n\n#### Signature\n\n```http\nPOST /org-management/users/{orgid}/send-reset (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Sends an email.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/users/{orgid}/change-password`","requestBody":{"description":"Which user.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"USR-4821"}}}}}},"/org-management/customers/{orgid}":{"get":{"operationId":"OrgManagementController_getOrgCustomers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"200":{"description":"Customers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"List an organization's customers","description":"Customers belonging to the named org. Customers are the org's end users, distinct from its staff users.\n\n#### Signature\n\n```http\nGET /org-management/customers/{orgid} (orgid: string) -> Customers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/users/{orgid}`"}},"/org-management/customers/{orgid}/{customerId}":{"get":{"operationId":"OrgManagementController_getOrgCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"},{"name":"customerId","required":true,"in":"path","schema":{"type":"string"},"description":"Customer id.","example":"CUST-4821"}],"responses":{"200":{"description":"The customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}/{customerId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Get an organization customer","description":"One customer in the named org.\n\n#### Signature\n\n```http\nGET /org-management/customers/{orgid}/{customerId} (orgid: string, customerId: string) -> The customer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/customers/{orgid}/update`"}},"/org-management/customers/{orgid}/create":{"post":{"operationId":"OrgManagementController_createOrgCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The customer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Create an organization customer","description":"Creates a customer in the named org.\n\n#### Signature\n\n```http\nPOST /org-management/customers/{orgid}/create (orgid: string, body) -> The customer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/customers/{orgid}/update`","requestBody":{"description":"The customer.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com","firstName":"Ada","lastName":"Lovelace"}}}}}},"/org-management/customers/{orgid}/update":{"post":{"operationId":"OrgManagementController_updateOrgCustomer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Update an organization customer","description":"Updates a customer in the named org.\n\n#### Signature\n\n```http\nPOST /org-management/customers/{orgid}/update (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/customers/{orgid}/status`","requestBody":{"description":"The customer id and fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"CUST-4821","phone":"+15551234567"}}}}}},"/org-management/customers/{orgid}/status":{"post":{"operationId":"OrgManagementController_setOrgCustomersStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Set organization customers' status","description":"Activates or deactivates customers in bulk. Deactivated customers cannot sign in.\n\n#### Signature\n\n```http\nPOST /org-management/customers/{orgid}/status (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/customers/{orgid}/delete`","requestBody":{"description":"Which customers, and the status.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ids":["CUST-4821"],"status":"inactive"}}}}}},"/org-management/customers/{orgid}/delete":{"post":{"operationId":"OrgManagementController_deleteOrgCustomers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}/delete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Delete organization customers","description":"Permanently removes customers. Their orders, bookings and history may reference them — deactivate unless the records genuinely must go.\n\n#### Signature\n\n```http\nPOST /org-management/customers/{orgid}/delete (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Irreversible; breaks references from historical records.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/customers/{orgid}/status`","requestBody":{"description":"Which customers.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ids":["CUST-4821"]}}}}}},"/org-management/customers/{orgid}/change-password":{"post":{"operationId":"OrgManagementController_resetOrgCustomerPassword","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}/change-password","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Change an organization customer's password","description":"Sets a customer's password directly. An administrative override, without the customer's involvement — prefer sending a reset link.\n\n#### Signature\n\n```http\nPOST /org-management/customers/{orgid}/change-password (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Sensitive payload; prefer `send-reset`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/customers/{orgid}/send-reset`","requestBody":{"description":"The customer and the new password.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"CUST-4821","password":"<new password>"}}}}}},"/org-management/customers/{orgid}/send-reset":{"post":{"operationId":"OrgManagementController_sendOrgCustomerReset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"path","schema":{"type":"string"},"description":"The org being acted on — separate from the `orgid` **header**, which identifies the caller.","example":"org_4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid orgId — The org id is missing or not a real org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid orgId","path":"/org-management/customers/{orgid}/send-reset","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"summary":"Send a password reset to an organization customer","description":"Emails a reset link to the customer. Requires the customer to have an email on file — without one there is nowhere to send it.\n\n#### Signature\n\n```http\nPOST /org-management/customers/{orgid}/send-reset (orgid: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`.\n\n#### Notes\n\n- Sends an email.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ORG_ID | Invalid orgId | The org id is missing or not a real org. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /org-management/customers/{orgid}/change-password`","requestBody":{"description":"Which customer.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"CUST-4821"}}}}}},"/org-management/transfer/requests":{"post":{"operationId":"OrgTransferController_createRequest","summary":"Ask another org to accept a data transfer","description":"The source org (the `orgid` header) asks a target org to accept a **copy** or **move** of records. Nothing moves until a ConfigAdmin of the target org accepts. Name what to send as `scope` — per collection, `select: all | filter | ids` — or with the older `assets: { datatype: [ids] }`. Every entry is counted up front; one request carries at most **100,000** records. The request expires after **7 days** if nobody decides. The target org gets an in-app notice and an email.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/requests (body) -> The request as the caller sees it\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TARGET_REQUIRED | targetOrgId is required | No target org. | — |\n| `404` | TARGET_NOT_FOUND | Target organization acme-eu not found | The target org does not exist. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /org-management/transfer/requests/check-org/{targetOrgId}`\n- `POST /org-management/transfer/requests/{id}/accept`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The request as the caller sees it","content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"status":{"type":"string","enum":["pending","accepted","running","completed","partial","failed","rejected","cancelled","expired"]},"direction":{"type":"string","enum":["incoming","outgoing"],"description":"Which side the caller is on."},"sourceOrgId":{"type":"string"},"targetOrgId":{"type":"string"},"mode":{"type":"string","enum":["copy","move"]},"datatypes":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"object","additionalProperties":true}},"totalRecords":{"type":"integer"},"processedRecords":{"type":"integer"},"reason":{"type":"string"},"message":{"type":"string"},"requestedBy":{"type":"string"},"requestedAt":{"type":"string"},"expiresAt":{"type":"string"},"acceptedBy":{"type":"string"},"rejectReason":{"type":"string"},"successCount":{"type":"integer"},"failCount":{"type":"integer"},"results":{"type":"object","additionalProperties":true},"error":{"type":"string"}}}}}},"400":{"description":"targetOrgId is required — No target org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"targetOrgId is required","path":"/org-management/transfer/requests","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Target organization acme-eu not found — The target org does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Target organization acme-eu not found","path":"/org-management/transfer/requests","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"What to send and to whom.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["targetOrgId"],"properties":{"targetOrgId":{"type":"string"},"scope":{"type":"array","items":{"type":"object","properties":{"datatype":{"type":"string"},"select":{"type":"string","enum":["all","filter","ids"]},"filter":{"type":"object","additionalProperties":true},"ids":{"type":"array","items":{"type":"string"}}}}},"assets":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},"mode":{"type":"string","enum":["copy","move"],"default":"copy"},"reason":{"type":"string"},"message":{"type":"string"}}},"examples":{"collections":{"summary":"Whole collections","value":{"targetOrgId":"acme-eu","mode":"copy","scope":[{"datatype":"sf_product","select":"all"}],"reason":"Opening the EU store"}},"rows":{"summary":"Chosen records","value":{"targetOrgId":"acme-eu","assets":{"page":["65f0c2a1e4b0a1b2c3d4e5f6"]}}}}}}}},"get":{"operationId":"OrgTransferController_listRequests","summary":"List transfer requests","description":"Requests where this org is the source (`outgoing`) or the target (`incoming`), newest first. A pending request past its expiry is reported as `expired` when read.\n\n#### Signature\n\n```http\nGET /org-management/transfer/requests (direction?: string, status?: string, page?: integer, pageSize?: integer) -> { data, total }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"direction","required":false,"in":"query","schema":{"type":"string","enum":["incoming","outgoing","all"],"default":"all"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["pending","accepted","running","completed","partial","failed","rejected","cancelled","expired"]}},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"{ data, total }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"requestId":{"type":"string"},"status":{"type":"string","enum":["pending","accepted","running","completed","partial","failed","rejected","cancelled","expired"]},"direction":{"type":"string","enum":["incoming","outgoing"],"description":"Which side the caller is on."},"sourceOrgId":{"type":"string"},"targetOrgId":{"type":"string"},"mode":{"type":"string","enum":["copy","move"]},"datatypes":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"object","additionalProperties":true}},"totalRecords":{"type":"integer"},"processedRecords":{"type":"integer"},"reason":{"type":"string"},"message":{"type":"string"},"requestedBy":{"type":"string"},"requestedAt":{"type":"string"},"expiresAt":{"type":"string"},"acceptedBy":{"type":"string"},"rejectReason":{"type":"string"},"successCount":{"type":"integer"},"failCount":{"type":"integer"},"results":{"type":"object","additionalProperties":true},"error":{"type":"string"}}}},"total":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/transfer/requests/check-org/{targetOrgId}":{"get":{"operationId":"OrgTransferController_checkOrg","summary":"Check a target org before sending","description":"Whether a transfer could name this org, and its display name — so the sender learns before building a request. Returns existence and name only.\n\n#### Signature\n\n```http\nGET /org-management/transfer/requests/check-org/{targetOrgId} (targetOrgId: string) -> { exists, orgId, name? , reason? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"targetOrgId","required":true,"in":"path","description":"Org id to check.","schema":{"type":"string"},"example":"acme-eu"}],"responses":{"200":{"description":"{ exists, orgId, name? , reason? }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"found":{"summary":"Exists","value":{"exists":true,"orgId":"acme-eu","name":"Acme EU"}},"self":{"summary":"Own org","value":{"exists":false,"orgId":"acme","reason":"That is this organization"}},"missing":{"summary":"Unknown","value":{"exists":false,"orgId":"acme-xx","reason":"No organization with that id"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/transfer/requests/{id}":{"get":{"operationId":"OrgTransferController_getRequest","summary":"Get one transfer request","description":"One request with its progress (`processedRecords` of `totalRecords`) and, once finished, `successCount`, `failCount` and `results`. Only the source or target org can read it; to anyone else it does not exist.\n\n#### Signature\n\n```http\nGET /org-management/transfer/requests/{id} (id: string) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TRANSFER_NOT_FOUND | Transfer request not found | No such request, or the caller's org is neither its source nor its target. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Transfer request id.","schema":{"type":"string"},"example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"200":{"description":"The request","content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"status":{"type":"string","enum":["pending","accepted","running","completed","partial","failed","rejected","cancelled","expired"]},"direction":{"type":"string","enum":["incoming","outgoing"],"description":"Which side the caller is on."},"sourceOrgId":{"type":"string"},"targetOrgId":{"type":"string"},"mode":{"type":"string","enum":["copy","move"]},"datatypes":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"object","additionalProperties":true}},"totalRecords":{"type":"integer"},"processedRecords":{"type":"integer"},"reason":{"type":"string"},"message":{"type":"string"},"requestedBy":{"type":"string"},"requestedAt":{"type":"string"},"expiresAt":{"type":"string"},"acceptedBy":{"type":"string"},"rejectReason":{"type":"string"},"successCount":{"type":"integer"},"failCount":{"type":"integer"},"results":{"type":"object","additionalProperties":true},"error":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transfer request not found — No such request, or the caller's org is neither its source nor its target.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transfer request not found","path":"/org-management/transfer/requests/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/transfer/requests/{id}/accept":{"post":{"operationId":"OrgTransferController_acceptRequest","summary":"Accept a transfer request","description":"The target org accepts. The request switches to `running` and comes back at once; the records are copied (or moved) in batches of 200 in the background, writing progress onto the request, and both orgs are told when it ends. Poll `GET …/requests/{id}` for progress.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/requests/{id}/accept (id: string) -> The request, now running\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TRANSFER_NOT_FOUND | Transfer request not found | No such request, or the caller's org is neither its source nor its target. | — |\n| `400` | NOT_PENDING | Transfer request is running, not pending | The request has already been decided, cancelled or has expired. | — |\n| `403` | NOT_TARGET | Only the target organization can accept a transfer request | The caller is the source org. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Transfer request id.","schema":{"type":"string"},"example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"The request, now running","content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"status":{"type":"string","enum":["pending","accepted","running","completed","partial","failed","rejected","cancelled","expired"]},"direction":{"type":"string","enum":["incoming","outgoing"],"description":"Which side the caller is on."},"sourceOrgId":{"type":"string"},"targetOrgId":{"type":"string"},"mode":{"type":"string","enum":["copy","move"]},"datatypes":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"object","additionalProperties":true}},"totalRecords":{"type":"integer"},"processedRecords":{"type":"integer"},"reason":{"type":"string"},"message":{"type":"string"},"requestedBy":{"type":"string"},"requestedAt":{"type":"string"},"expiresAt":{"type":"string"},"acceptedBy":{"type":"string"},"rejectReason":{"type":"string"},"successCount":{"type":"integer"},"failCount":{"type":"integer"},"results":{"type":"object","additionalProperties":true},"error":{"type":"string"}}}}}},"400":{"description":"Transfer request is running, not pending — The request has already been decided, cancelled or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Transfer request is running, not pending","path":"/org-management/transfer/requests/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the target organization can accept a transfer request — The caller is the source org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the target organization can accept a transfer request","path":"/org-management/transfer/requests/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Transfer request not found — No such request, or the caller's org is neither its source nor its target.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transfer request not found","path":"/org-management/transfer/requests/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/org-management/transfer/requests/{id}/reject":{"post":{"operationId":"OrgTransferController_rejectRequest","summary":"Reject a transfer request","description":"The target org declines, optionally saying why. The source org is told.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/requests/{id}/reject (id: string, body) -> The request, now rejected\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TRANSFER_NOT_FOUND | Transfer request not found | No such request, or the caller's org is neither its source nor its target. | — |\n| `400` | NOT_PENDING | Transfer request is running, not pending | The request has already been decided, cancelled or has expired. | — |\n| `403` | NOT_TARGET | Only the target organization can reject a transfer request | The caller is the source org. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Transfer request id.","schema":{"type":"string"},"example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"The request, now rejected","content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"status":{"type":"string","enum":["pending","accepted","running","completed","partial","failed","rejected","cancelled","expired"]},"direction":{"type":"string","enum":["incoming","outgoing"],"description":"Which side the caller is on."},"sourceOrgId":{"type":"string"},"targetOrgId":{"type":"string"},"mode":{"type":"string","enum":["copy","move"]},"datatypes":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"object","additionalProperties":true}},"totalRecords":{"type":"integer"},"processedRecords":{"type":"integer"},"reason":{"type":"string"},"message":{"type":"string"},"requestedBy":{"type":"string"},"requestedAt":{"type":"string"},"expiresAt":{"type":"string"},"acceptedBy":{"type":"string"},"rejectReason":{"type":"string"},"successCount":{"type":"integer"},"failCount":{"type":"integer"},"results":{"type":"object","additionalProperties":true},"error":{"type":"string"}}}}}},"400":{"description":"Transfer request is running, not pending — The request has already been decided, cancelled or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Transfer request is running, not pending","path":"/org-management/transfer/requests/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the target organization can reject a transfer request — The caller is the source org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the target organization can reject a transfer request","path":"/org-management/transfer/requests/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Transfer request not found — No such request, or the caller's org is neither its source nor its target.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transfer request not found","path":"/org-management/transfer/requests/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"],"requestBody":{"description":"Why.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Wrong catalogue"}}}}}},"/org-management/transfer/requests/{id}/cancel":{"post":{"operationId":"OrgTransferController_cancelRequest","summary":"Cancel a transfer request","description":"The source org withdraws a request before the target decides. The target is told.\n\n#### Signature\n\n```http\nPOST /org-management/transfer/requests/{id}/cancel (id: string) -> The request, now cancelled\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TRANSFER_NOT_FOUND | Transfer request not found | No such request, or the caller's org is neither its source nor its target. | — |\n| `400` | NOT_PENDING | Transfer request is running, not pending | The request has already been decided, cancelled or has expired. | — |\n| `403` | NOT_SOURCE | Only the source organization can cancel a transfer request | The caller is the target org. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Transfer request id.","schema":{"type":"string"},"example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"The request, now cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"status":{"type":"string","enum":["pending","accepted","running","completed","partial","failed","rejected","cancelled","expired"]},"direction":{"type":"string","enum":["incoming","outgoing"],"description":"Which side the caller is on."},"sourceOrgId":{"type":"string"},"targetOrgId":{"type":"string"},"mode":{"type":"string","enum":["copy","move"]},"datatypes":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"object","additionalProperties":true}},"totalRecords":{"type":"integer"},"processedRecords":{"type":"integer"},"reason":{"type":"string"},"message":{"type":"string"},"requestedBy":{"type":"string"},"requestedAt":{"type":"string"},"expiresAt":{"type":"string"},"acceptedBy":{"type":"string"},"rejectReason":{"type":"string"},"successCount":{"type":"integer"},"failCount":{"type":"integer"},"results":{"type":"object","additionalProperties":true},"error":{"type":"string"}}}}}},"400":{"description":"Transfer request is running, not pending — The request has already been decided, cancelled or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Transfer request is running, not pending","path":"/org-management/transfer/requests/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the source organization can cancel a transfer request — The caller is the target org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the source organization can cancel a transfer request","path":"/org-management/transfer/requests/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Transfer request not found — No such request, or the caller's org is neither its source nor its target.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transfer request not found","path":"/org-management/transfer/requests/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Organization"]}},"/ai/mcp/tools":{"get":{"operationId":"AIController_getMcpTools","summary":"List MCP tools","description":"The tools the model can call against this org's data. Read this to know what a chat turn is capable of doing — the list is the blast radius.\n\n#### Signature\n\n```http\nGET /ai/mcp/tools () -> { tools }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/mcp/execute`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"{ tools }","content":{"application/json":{"schema":{"type":"object","properties":{"tools":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/mcp/execute":{"post":{"operationId":"AIController_executeMcpTool","summary":"Execute an MCP tool","description":"Runs one tool directly, without going through the model. Useful for testing what a tool actually does before letting a conversation call it.\n\nTools act on real org data: a write tool writes. The arguments are passed to the tool as given.\n\n#### Signature\n\n```http\nPOST /ai/mcp/execute (body) -> The tool result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Tools can modify org data.\n- A missing `toolName` fails as a server error (500), not a 400.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /ai/mcp/tools`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The tool and its arguments.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["toolName"],"properties":{"toolName":{"type":"string","example":"search_customers"},"args":{"type":"object","additionalProperties":true,"description":"`orgId` and `userId` are filled from the caller when not given.","example":{"query":"ada"}}}},"example":{"toolName":"search_customers","args":{"query":"ada"}}}}},"responses":{"201":{"description":"The tool result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/chat":{"post":{"operationId":"AIController_chat","summary":"Chat with AI","description":"A single chat turn. Files attached under `files` are processed and made available to the model, so a document can be asked about directly.\n\nTwo ways to carry history: send prior turns in `conversationHistory`, or pass a `conversationId` and let the server hold the memory. The second is cheaper — `conversationHistory` is re-sent and re-billed on every call.\n\n`availableTools` narrows what the model may call; leaving it out means the full MCP tool set is in play.\n\n#### Signature\n\n```http\nPOST /ai/chat (body) -> The chat response\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Consumes billable AI credit.\n- Tool calls can modify org data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/agent/chat`\n- `POST /ai/agent/stream`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The chat request.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"type":"string","description":"The user message or task.","example":"Summarise last month's refunds."},"conversationHistory":{"type":"array","description":"Prior turns. Re-sent each call, so a long history costs more.","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"],"example":"user"},"content":{"type":"string"}}}},"context":{"type":"object","description":"Additional context for the model. `context.agent` picks the role the assistant plays (default `chat`).","additionalProperties":true},"conversationId":{"type":"string","description":"Server-side memory key — carries history without re-sending it.","example":"CONV-4821"},"memoryTtl":{"type":"number","description":"How long that memory lives, in seconds.","example":3600},"clientMcpTools":{"type":"array","description":"Tools the client itself can run.","items":{"type":"object","additionalProperties":true}},"availableTools":{"type":"array","description":"Restrict which tools the model may call.","items":{"type":"object","additionalProperties":true}},"files":{"type":"array","description":"Documents to process alongside the message.","items":{"type":"object","properties":{"path":{"type":"string","description":"File path in storage.","example":"uploads/spec.pdf"},"name":{"type":"string","example":"spec.pdf"},"url":{"type":"string","description":"Optional.","example":"https://cdn.example.com/uploads/spec.pdf"},"size":{"type":"number","example":184320}}}}}},"examples":{"simple":{"summary":"A single question","value":{"task":"Summarise last month's refunds."}},"withMemory":{"summary":"Continuing a conversation","value":{"task":"And the month before?","conversationId":"CONV-4821","memoryTtl":3600}},"withFile":{"summary":"Ask about a document","value":{"task":"What are the payment terms?","files":[{"path":"uploads/contract.pdf","name":"contract.pdf"}]}}}}}},"responses":{"201":{"description":"The chat response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/agent/chat":{"post":{"operationId":"AIController_agentChat","summary":"Chat with an AI agent","description":"A chat turn where the caller chooses the role the assistant plays (`agentRole`, or a role named in `aiMode`) and can add system instructions (`agentInstructions`). Same file handling and memory options as `POST /ai/chat`.\n\n#### Signature\n\n```http\nPOST /ai/agent/chat (body) -> The chat response\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Consumes billable AI credit.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/chat`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The chat request, plus the role and instructions.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"type":"string","description":"The user message or task.","example":"Summarise last month's refunds."},"conversationHistory":{"type":"array","description":"Prior turns. Re-sent each call, so a long history costs more.","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"],"example":"user"},"content":{"type":"string"}}}},"context":{"type":"object","description":"Additional context for the model. `context.agent` picks the role the assistant plays (default `chat`).","additionalProperties":true},"conversationId":{"type":"string","description":"Server-side memory key — carries history without re-sending it.","example":"CONV-4821"},"memoryTtl":{"type":"number","description":"How long that memory lives, in seconds.","example":3600},"clientMcpTools":{"type":"array","description":"Tools the client itself can run.","items":{"type":"object","additionalProperties":true}},"availableTools":{"type":"array","description":"Restrict which tools the model may call.","items":{"type":"object","additionalProperties":true}},"files":{"type":"array","description":"Documents to process alongside the message.","items":{"type":"object","properties":{"path":{"type":"string","description":"File path in storage.","example":"uploads/spec.pdf"},"name":{"type":"string","example":"spec.pdf"},"url":{"type":"string","description":"Optional.","example":"https://cdn.example.com/uploads/spec.pdf"},"size":{"type":"number","example":184320}}}},"agentRole":{"type":"string","description":"The role the assistant plays, by name. Defaults to `chat`.","example":"chat"},"aiMode":{"type":"string","description":"Older clients send the role here; it selects the role only when it names one, otherwise it is mode data the chat role reads.","example":"ai-agent"},"agentInstructions":{"type":"string","description":"Extra system instructions for this turn."}}},"example":{"agentRole":"chat","agentInstructions":"Answer as the support desk.","task":"Draft a reply to ticket 4821."}}}},"responses":{"201":{"description":"The chat response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/ui/ack":{"post":{"operationId":"AIController_uiActionAck","summary":"Confirm a UI action landed","description":"The client telling the server that a UI action the assistant asked for (e.g. `navigate`) actually happened. The waiting tool call is released with what the client reports, so the assistant can say \"done\" rather than \"dispatched, not confirmed\". Post it once the route is mounted or the change is made; `result` carries what the client did (e.g. ids of objects it added) back to the model.\n\n#### Signature\n\n```http\nPOST /ai/ui/ack (body) -> { received: true }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/agent/stream`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What happened.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversationId":{"type":"string","description":"Defaults to `default`."},"action":{"type":"string","description":"Defaults to `navigate`.","example":"navigate"},"route":{"type":"string","example":"/crm/leads"},"view":{"type":"string"},"ok":{"type":"boolean","description":"false when it failed; anything else counts as success."},"error":{"type":"string"},"result":{"type":"object","additionalProperties":true}}},"example":{"conversationId":"CONV-4821","action":"navigate","route":"/crm/leads","ok":true}}}},"responses":{"201":{"description":"{ received: true }","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean"}}},"example":{"received":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/agent/stream":{"post":{"operationId":"AIController_chatStream","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"authorization","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"{ streamId }","content":{"application/json":{"schema":{"type":"object","properties":{"streamId":{"type":"string"}}},"example":{"streamId":"STR-4821"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"description":"Insufficient balance to use AI services — The org has no subscription and not enough balance for the minimum estimated cost.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"code":"INSUFFICIENT_BALANCE","message":"Insufficient balance to use AI services","required":0.01,"available":0,"action":"add_credits","billingUrl":"/billing"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"],"summary":"Start a streaming chat","description":"Starts a streaming response and returns a **stream id** — it does not return the text. Connect to `GET /ai/stream/{streamId}` to read it as it arrives.\n\nThe two-step shape exists because the generation outlives the request; a client that only calls this and never connects has still paid for the generation.\n\nWith a `conversationId`, the history is the **server's**: the last 20 messages of that conversation replace any `conversationHistory` sent. The user message and the reply are saved to the conversation. Tool calls the assistant makes run as the caller (their bearer token), so their own permissions apply.\n\nThe org needs a subscription or a positive balance to start.\n\n#### Signature\n\n```http\nPOST /ai/agent/stream (body) -> { streamId }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Consumes billable AI credit.\n- Returns a stream id, not the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `402` | INSUFFICIENT_BALANCE | Insufficient balance to use AI services | The org has no subscription and not enough balance for the minimum estimated cost. | Add credits (billingUrl) and retry. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /ai/stream/{streamId}`","requestBody":{"description":"The chat request.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"type":"string","description":"The user message or task.","example":"Summarise last month's refunds."},"conversationHistory":{"type":"array","description":"Prior turns. Re-sent each call, so a long history costs more.","items":{"type":"object","properties":{"role":{"type":"string","enum":["user","assistant"],"example":"user"},"content":{"type":"string"}}}},"context":{"type":"object","description":"Additional context for the model. `context.agent` picks the role the assistant plays (default `chat`).","additionalProperties":true},"conversationId":{"type":"string","description":"Server-side memory key — carries history without re-sending it.","example":"CONV-4821"},"memoryTtl":{"type":"number","description":"How long that memory lives, in seconds.","example":3600},"clientMcpTools":{"type":"array","description":"Tools the client itself can run.","items":{"type":"object","additionalProperties":true}},"availableTools":{"type":"array","description":"Restrict which tools the model may call.","items":{"type":"object","additionalProperties":true}},"files":{"type":"array","description":"Documents to process alongside the message.","items":{"type":"object","properties":{"path":{"type":"string","description":"File path in storage.","example":"uploads/spec.pdf"},"name":{"type":"string","example":"spec.pdf"},"url":{"type":"string","description":"Optional.","example":"https://cdn.example.com/uploads/spec.pdf"},"size":{"type":"number","example":184320}}}},"agentRole":{"type":"string","description":"The role the assistant plays, by name. Defaults to `chat`.","example":"chat"},"aiMode":{"type":"string","description":"Older clients send the role here; it selects the role only when it names one, otherwise it is mode data the chat role reads.","example":"ai-agent"},"agentInstructions":{"type":"string","description":"Extra system instructions for this turn."},"images":{"type":"array","description":"Images to send to the model with the message.","items":{"type":"object","additionalProperties":true}},"settings":{"type":"object","additionalProperties":true,"description":"Model options passed through (e.g. model, temperature)."}}},"example":{"task":"Write a summary of this quarter.","conversationId":"CONV-4821"}}}}}},"/ai/stream/{streamId}":{"get":{"operationId":"AIController_streamEvents","summary":"Read a stream","description":"A **Server-Sent Events** connection to a stream started by `POST /ai/agent/stream`. The first event is `data` with `{ type: \"status\", status: \"connected\" }`; chunks already produced are then **replayed**, so a client that reconnects does not lose the start. Events: `data` (a chunk as JSON), `end` (done), `error` (`{ message }` — the provider's own reason when it refused).\n\nAn unknown stream id, or a missing `orgid` header, answers with a single `error` event and closes — not an HTTP error status.\n\n#### Signature\n\n```http\nGET /ai/stream/{streamId} (streamId: string) -> The event stream\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/agent/stream`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"streamId","required":true,"in":"path","description":"Stream id from the stream start call.","schema":{"type":"string"},"example":"STR-4821"}],"responses":{"200":{"description":"The event stream","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/generate/image":{"post":{"operationId":"AIController_generateImage","summary":"Generate an image","description":"One endpoint covering three modes, chosen by what is in the body:\n\n- **From a prompt** — `prompt` alone creates a new image.\n- **With references** — `prompt` plus `images` guides generation from existing pictures.\n- **Inpainting** — `prompt` plus an image and a `mask` edits the masked region.\n\nThis replaces the deprecated variations and edit endpoints; new code should use this one.\n\n#### Signature\n\n```http\nPOST /ai/generate/image (body) -> The provider response plus `files` (the images saved to the org's storage) and a `message`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Consumes billable AI credit.\n- Each image in `n` is billed separately.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | IMAGE_GENERATION_FAILED | <the provider's reason> | Any failure — including \"No AI provider found\" and a provider that does not make images — comes back as a 400 carrying the reason. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/generate/video`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The generation request.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string","description":"The text prompt.","example":"A cola bottle on a marble counter, studio lighting"},"images":{"type":"array","description":"Reference images for guided generation.","items":{"type":"object","properties":{"path":{"type":"string","example":"uploads/ref.jpg"},"url":{"type":"string"}}}},"mask":{"type":"object","description":"Mask for inpainting — the region to replace.","additionalProperties":true},"size":{"type":"string","example":"1024x1024"},"n":{"type":"integer","description":"How many to generate (default 1). Each one is billed.","example":1},"variations":{"type":"integer","description":"Alias for `n`."},"quality":{"type":"string","example":"hd"},"style":{"type":"string","example":"natural"},"provider":{"type":"string","description":"Which configured AI provider to use; default is the platform's image provider."}}},"examples":{"fromPrompt":{"summary":"From a prompt","value":{"prompt":"A cola bottle on a marble counter, studio lighting","size":"1024x1024"}},"withReference":{"summary":"Guided by a reference image","value":{"prompt":"Same bottle, on a wooden table","images":[{"path":"uploads/ref.jpg"}]}},"inpaint":{"summary":"Edit a masked region","value":{"prompt":"Replace the label with a plain white one","images":[{"path":"uploads/bottle.jpg"}],"mask":{"path":"uploads/bottle-mask.png"}}}}}}},"responses":{"201":{"description":"The provider response plus `files` (the images saved to the org's storage) and a `message`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"<the provider's reason> — Any failure — including \"No AI provider found\" and a provider that does not make images — comes back as a 400 carrying the reason.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"<the provider's reason>","path":"/ai/generate/image","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/generate/image/variations":{"post":{"operationId":"AIController_generateImageVariations","summary":"Generate image variations (deprecated)","description":"Deprecated. Use `POST /ai/generate/image` with `images` as references — it covers this and more. Kept for existing callers.\n\n#### Signature\n\n```http\nPOST /ai/generate/image/variations (body) -> The variations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Deprecated.\n- Consumes billable AI credit.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/generate/image`","deprecated":true,"parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The request.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","description":"The reference image (becomes `images: [{ path }]`)."},"images":{"type":"array","items":{"type":"object","additionalProperties":true}},"prompt":{"type":"string"},"n":{"type":"integer"}}},"example":{"path":"uploads/ref.jpg","n":2}}}},"responses":{"201":{"description":"The variations","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/generate/image/edit":{"post":{"operationId":"AIController_editImage","summary":"Edit an image (deprecated)","description":"Deprecated. Use `POST /ai/generate/image` with a `mask` for inpainting. Kept for existing callers.\n\n#### Signature\n\n```http\nPOST /ai/generate/image/edit (body) -> The edited image\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Deprecated.\n- Consumes billable AI credit.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/generate/image`","deprecated":true,"parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The request.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string"},"imagePath":{"type":"string","description":"Becomes `images: [{ path }]`."},"maskPath":{"type":"string","description":"Becomes `mask: { path }`."},"images":{"type":"array","items":{"type":"object","additionalProperties":true}},"mask":{"type":"object","additionalProperties":true}}},"example":{"prompt":"Plain white label","imagePath":"uploads/bottle.jpg","maskPath":"uploads/bottle-mask.png"}}}},"responses":{"201":{"description":"The edited image","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/ai/generate/video":{"post":{"operationId":"AIController_generateVideo","summary":"Generate a video","description":"Generates video from a prompt with a provider that supports video. The request waits for the result, which is slow — considerably slower and more expensive than an image. Each video returned is saved to the org's storage.\n\n#### Signature\n\n```http\nPOST /ai/generate/video (body) -> The provider response plus `files` (the saved videos) and a `message`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Consumes billable AI credit.\n- Slow and comparatively expensive.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | VIDEO_GENERATION_FAILED | <the provider's reason> | No AI provider, a provider that does not support video (\"Provider <name> does not support video generation\"), or the provider refused. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai/generate/image`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The generation request, passed to the provider.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string"},"provider":{"type":"string","description":"A configured provider that makes video."}},"additionalProperties":true},"example":{"prompt":"A slow pan across a city skyline at dusk"}}}},"responses":{"201":{"description":"The provider response plus `files` (the saved videos) and a `message`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"<the provider's reason> — No AI provider, a provider that does not support video (\"Provider <name> does not support video generation\"), or the provider refused.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"<the provider's reason>","path":"/ai/generate/video","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI"]}},"/client-data/dashboard":{"get":{"operationId":"ClientAccountController_getClientDashboard","summary":"Get my dashboard","description":"A consolidated view for the signed-in customer — recent orders, upcoming reservations, open tickets and unread messages in one call, so an account home page does not need six requests.\n\n#### Signature\n\n```http\nGET /client-data/dashboard () -> The dashboard\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/all`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The dashboard","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/dashboard","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/shared-accounts/{accountId}/members":{"get":{"operationId":"ClientAccountController_getSharedAccountMembers","summary":"List shared account members","description":"Managers only. Everyone associated with the account, suspended members included. A member whose customer record is gone is kept (`missing: true`) so it can still be removed.\n\n#### Signature\n\n```http\nGET /client-data/shared-accounts/{accountId}/members (accountId: string) -> [{ associationId, customerId, name, email, role, status, missing }]\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `403` | NOT_MANAGER | You do not manage this account | The caller has no active manager association with this account. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"accountId","required":true,"in":"path","description":"Shared account id (the account `customer` record).","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"[{ associationId, customerId, name, email, role, status, missing }]","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/shared-accounts/{accountId}/members","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You do not manage this account — The caller has no active manager association with this account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You do not manage this account","path":"/client-data/shared-accounts/{accountId}/members","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/shared-accounts/{accountId}/orders":{"get":{"operationId":"ClientAccountController_getSharedAccountOrders","summary":"List shared account orders","description":"Managers only. Orders placed on the account by any member.\n\n#### Signature\n\n```http\nGET /client-data/shared-accounts/{accountId}/orders (accountId: string, page?: integer, limit?: integer) -> Paged orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `403` | NOT_MANAGER | You do not manage this account | The caller has no active manager association with this account. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"accountId","required":true,"in":"path","description":"Shared account id (the account `customer` record).","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Rows per page.","example":20}],"responses":{"200":{"description":"Paged orders","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/shared-accounts/{accountId}/orders","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You do not manage this account — The caller has no active manager association with this account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You do not manage this account","path":"/client-data/shared-accounts/{accountId}/orders","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/shared-accounts/{accountId}/members/{associationId}":{"put":{"operationId":"ClientAccountController_updateSharedAccountMember","summary":"Change a shared account member","description":"Managers only. Changes a member's `role` (manager | buyer) or `status` (active | suspended). Suspending keeps the association so their past orders still explain themselves. The account must keep at least one active manager.\n\n#### Signature\n\n```http\nPUT /client-data/shared-accounts/{accountId}/members/{associationId} (accountId: string, associationId: string, body) -> The association\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `403` | NOT_MANAGER | You do not manage this account | The caller has no active manager association with this account. | — |\n| `404` | MEMBER_NOT_FOUND | Member not found on this account | The association does not exist or belongs to another account. | — |\n| `400` | LAST_MANAGER | The account must keep at least one manager | The change would leave no active manager. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"associationId","required":true,"in":"path","schema":{"type":"string"},"description":"customer_association sk."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["manager","buyer"]},"status":{"type":"string","enum":["active","suspended"]}}},"example":{"status":"suspended"}}}},"responses":{"200":{"description":"The association","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The account must keep at least one manager — The change would leave no active manager.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The account must keep at least one manager","path":"/client-data/shared-accounts/{accountId}/members/{associationId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/shared-accounts/{accountId}/members/{associationId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You do not manage this account — The caller has no active manager association with this account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You do not manage this account","path":"/client-data/shared-accounts/{accountId}/members/{associationId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Member not found on this account — The association does not exist or belongs to another account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Member not found on this account","path":"/client-data/shared-accounts/{accountId}/members/{associationId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/shared-accounts/{accountId}/invites":{"post":{"operationId":"ClientAccountController_inviteToSharedAccount","summary":"Invite someone to a shared account","description":"Managers only. An email that already belongs to a customer is associated straight away (a suspended member is reinstated) and told by email; a new email gets a pending invitation with a sign-up link on the storefront the manager is on (`x-client-host`). Resending to a pending invite re-sends it. `role` defaults to buyer.\n\n#### Signature\n\n```http\nPOST /client-data/shared-accounts/{accountId}/invites (accountId: string, body) -> `{ added, reinstated, customerId }` or `{ invited, resent, email }`, plus the assembled `account`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `403` | NOT_MANAGER | You do not manage this account | The caller has no active manager association with this account. | — |\n| `400` | EMAIL_REQUIRED | A valid email is required | `email` missing or has no @. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Shared account id (the account `customer` record).","example":"66f1a2b3c4d5e6f708192a3b"},{"name":"x-client-host","required":false,"in":"header","schema":{"type":"string"},"description":"The storefront host the invitation link points at."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"role":{"type":"string","enum":["manager","buyer"]},"message":{"type":"string"}}},"example":{"email":"buyer@acme.com","role":"buyer","message":"Welcome to the Acme account"}}}},"responses":{"201":{"description":"`{ added, reinstated, customerId }` or `{ invited, resent, email }`, plus the assembled `account`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid email is required — `email` missing or has no @.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid email is required","path":"/client-data/shared-accounts/{accountId}/invites","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/shared-accounts/{accountId}/invites","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You do not manage this account — The caller has no active manager association with this account.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You do not manage this account","path":"/client-data/shared-accounts/{accountId}/invites","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/shared-accounts/invites/validate/{invitationToken}":{"get":{"operationId":"ClientAccountController_validateSharedAccountInvitation","summary":"Check a shared account invitation","description":"Public. Validates an invitation link before the sign-up form is shown. An expired link is marked expired and refused.\n\n#### Signature\n\n```http\nGET /client-data/shared-accounts/invites/validate/{invitationToken} (invitationToken: string) -> { email, firstName, lastName, invitedByName, message, accountName }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVITE_INVALID | Invalid or expired invitation | No pending invitation has that token. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"invitationToken","required":true,"in":"path","schema":{"type":"string"},"description":"Token from the invitation email."}],"responses":{"200":{"description":"{ email, firstName, lastName, invitedByName, message, accountName }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid or expired invitation — No pending invitation has that token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or expired invitation","path":"/client-data/shared-accounts/invites/validate/{invitationToken}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/shared-accounts/invites/accept/{invitationToken}":{"post":{"operationId":"ClientAccountController_acceptSharedAccountInvitation","summary":"Accept a shared account invitation","description":"Once the invitee is signed in (just signed up through the link, or already had an account), associates them with the account named on the invitation with its role.\n\n#### Signature\n\n```http\nPOST /client-data/shared-accounts/invites/accept/{invitationToken} (invitationToken: string) -> { accepted: true, accountId, role }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_SIGNED_IN | Not signed in | No signed-in customer. | — |\n| `400` | INVITE_INVALID | Invalid or expired invitation | No pending invitation has that token. | — |\n| `403` | WRONG_EMAIL | This invitation was sent to a different email | The signed-in email is not the invited one. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"invitationToken","required":true,"in":"path","schema":{"type":"string"},"description":"Token from the invitation email."}],"responses":{"201":{"description":"{ accepted: true, accountId, role }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid or expired invitation — No pending invitation has that token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or expired invitation","path":"/client-data/shared-accounts/invites/accept/{invitationToken}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not signed in — No signed-in customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not signed in","path":"/client-data/shared-accounts/invites/accept/{invitationToken}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This invitation was sent to a different email — The signed-in email is not the invited one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This invitation was sent to a different email","path":"/client-data/shared-accounts/invites/accept/{invitationToken}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/profile":{"get":{"operationId":"ClientAccountController_getProfile","summary":"Get my profile","description":"The signed-in customer's profile.\n\n#### Signature\n\n```http\nGET /client-data/profile () -> The profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client-data/profile`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/profile","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"put":{"operationId":"ClientAccountController_updateProfile","summary":"Update my profile","description":"Updates the customer's own profile. Changing an email here does not re-verify it — use the verification endpoints if the new address needs proving.\n\n#### Signature\n\n```http\nPUT /client-data/profile (body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/verification/email/send`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"firstName":"Ada","lastName":"Lovelace","phone":"+15551234567"}}}},"responses":{"200":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/profile","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/orders":{"get":{"operationId":"ClientAccountController_getOrders","summary":"Get my orders","description":"The customer's order history, paged.\n\n#### Signature\n\n```http\nGET /client-data/orders (page?: integer, limit?: integer, status?: string, sortBy?: string, sortOrder?: string) -> Orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/orders/{orderId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Rows per page.","example":20},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Order status."},{"name":"sortBy","required":false,"in":"query","schema":{"type":"string"}},{"name":"sortOrder","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"Orders","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/orders","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/forms":{"get":{"operationId":"ClientAccountController_getForms","summary":"Get my forms","description":"Forms this customer has submitted and any still waiting on them, matched on their email. A `pending` row is a link that was asked for and not used yet (`submittedAt` null).\n\n#### Signature\n\n```http\nGET /client-data/forms (page?: integer, limit?: integer, status?: string) -> { data, total, page, pageSize }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Rows per page.","example":20},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Submission status, e.g. `pending`."}],"responses":{"200":{"description":"{ data, total, page, pageSize }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/forms","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/orders/{orderId}":{"get":{"operationId":"ClientAccountController_getOrder","summary":"Get one of my orders","description":"Fetches one of the customer's own orders. An order belonging to someone else is not returned.\n\n#### Signature\n\n```http\nGET /client-data/orders/{orderId} (orderId: string) -> The order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/transactions`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"orderId","required":true,"in":"path","description":"Order id or number.","schema":{"type":"string"},"example":"A7K2M9QX4"}],"responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/orders/{orderId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/reservations":{"get":{"operationId":"ClientAccountController_getReservations","summary":"Get my reservations","description":"The customer's bookings, past and upcoming.\n\n#### Signature\n\n```http\nGET /client-data/reservations () -> Reservations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/reservations`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Reservations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/reservations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"post":{"operationId":"ClientAccountController_createReservation","summary":"Make a reservation","description":"Books a reservation for the customer.\n\n#### Signature\n\n```http\nPOST /client-data/reservations (body) -> The created reservation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/reservations/available-slots`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}}],"requestBody":{"description":"The reservation.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"servicePointId":"sp_t7","startDate":"2026-10-05T10:00:00.000Z","endDate":"2026-10-05T10:30:00.000Z"}}}},"responses":{"201":{"description":"The created reservation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/reservations","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"put":{"operationId":"ClientAccountController_updateReservation","summary":"Update my reservation","description":"Reschedules or amends one of the customer's reservations. Availability is not re-checked automatically — confirm the new slot is free first.\n\n#### Signature\n\n```http\nPUT /client-data/reservations (body) -> The updated reservation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client-data/reservations/{reservationId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}}],"requestBody":{"description":"The reservation to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reservationId":"RES-4821","startDate":"2026-10-06T10:00:00.000Z"}}}},"responses":{"200":{"description":"The updated reservation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/reservations","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/reservations/{reservationId}":{"get":{"operationId":"ClientAccountController_getReservation","summary":"Get one of my reservations","description":"Fetches one of the customer's own reservations.\n\n#### Signature\n\n```http\nGET /client-data/reservations/{reservationId} (reservationId: string) -> The reservation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client-data/reservations`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"reservationId","required":true,"in":"path","description":"Reservation id.","schema":{"type":"string"},"example":"RES-4821"}],"responses":{"200":{"description":"The reservation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/reservations/{reservationId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"delete":{"operationId":"ClientAccountController_cancelReservation","summary":"Cancel my reservation","description":"Cancels one of the customer's reservations, freeing the slot.\n\n#### Signature\n\n```http\nDELETE /client-data/reservations/{reservationId} (reservationId: string) -> Cancellation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/reservations`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"reservationId","required":true,"in":"path","description":"Reservation id.","schema":{"type":"string"},"example":"RES-4821"}],"responses":{"200":{"description":"Cancellation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/reservations/{reservationId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/reservations/available-slots":{"post":{"operationId":"ClientAccountController_getAvailableSlots","summary":"Find available slots","description":"Computes bookable slots for a service or location. Slots are calculated, not held — one shown here can be taken before the customer books it.\n\n#### Signature\n\n```http\nPOST /client-data/reservations/available-slots (body) -> Available slots\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No hold is placed — handle a conflict on booking.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/reservations`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to check availability for.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"servicePointId":"sp_t7","startDate":"2026-10-05","endDate":"2026-10-06","duration":30}}}},"responses":{"201":{"description":"Available slots","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/reservations/available-slots","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/tickets":{"get":{"operationId":"ClientAccountController_getTickets","summary":"Get my support tickets","description":"The customer's own support tickets.\n\n#### Signature\n\n```http\nGET /client-data/tickets (status?: string, enrich?: string) -> Tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/tickets`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"}},{"name":"enrich","required":false,"in":"query","schema":{"type":"string","enum":["true","false"]},"description":"Include linked records."}],"responses":{"200":{"description":"Tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/tickets","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"post":{"operationId":"ClientAccountController_createTicket","summary":"Raise a support ticket","description":"Creates a support ticket as the signed-in customer.\n\n#### Signature\n\n```http\nPOST /client-data/tickets (body) -> The created ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/tickets/with-attachments`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The ticket.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Order never arrived","description":"Tracking has not updated in a week","orderNumber":"A7K2M9QX4"}}}},"responses":{"201":{"description":"The created ticket","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/tickets","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"put":{"operationId":"ClientAccountController_updateTicket","summary":"Update my ticket","description":"Adds to or amends one of the customer's tickets — replying, or supplying detail that was asked for.\n\n#### Signature\n\n```http\nPUT /client-data/tickets (body) -> The updated ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/tickets/{ticketNumber}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The ticket update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ticketNumber":"TKT-4821","message":"Tracking updated today — please close this"}}}},"responses":{"200":{"description":"The updated ticket","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/tickets","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/tickets/{ticketNumber}":{"get":{"operationId":"ClientAccountController_getTicket","summary":"Get one of my tickets","description":"Fetches one ticket with its customer-visible conversation. Internal staff comments are not included.\n\n#### Signature\n\n```http\nGET /client-data/tickets/{ticketNumber} (ticketNumber: string) -> The ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only the customer-facing thread is returned — internal comments stay internal.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client-data/tickets`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"ticketNumber","required":true,"in":"path","description":"Ticket number.","schema":{"type":"string"},"example":"TKT-4821"}],"responses":{"200":{"description":"The ticket","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/tickets/{ticketNumber}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/tickets/with-attachments":{"post":{"operationId":"ClientAccountController_createTicketWithAttachments","summary":"Raise a ticket with attachments","description":"Creates a ticket and uploads supporting files in one multipart request — screenshots and photos, which is what most support issues need.\n\n#### Signature\n\n```http\nPOST /client-data/tickets/with-attachments (body) -> The created ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/tickets`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"Multipart form with the ticket fields and files.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"files":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"201":{"description":"The created ticket","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/tickets/with-attachments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/messages":{"get":{"operationId":"ClientAccountController_getMessages","summary":"Get my messages","description":"Messages sent to and from the customer.\n\n#### Signature\n\n```http\nGET /client-data/messages (page?: integer, pageSize?: integer, sort?: string, sortType?: string, conversationWith?: string) -> Messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/conversations`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"conversationWith","required":false,"in":"query","schema":{"type":"string"},"description":"Only the thread with this address."},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"sort","required":false,"in":"query","schema":{"type":"string"},"description":"Field to sort by."},{"name":"sortType","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/messages","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"post":{"operationId":"ClientAccountController_sendMessage","summary":"Send a message","description":"Sends a message as the customer.\n\n#### Signature\n\n```http\nPOST /client-data/messages (send?: boolean, body) -> The sent message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/messages`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"send","required":false,"in":"query","schema":{"type":"boolean"},"description":"true to send immediately; otherwise saved as a draft."}],"requestBody":{"description":"The message.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"subject":"Question about delivery","body":"When do you ship to Ireland?"}}}},"responses":{"201":{"description":"The sent message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/conversations":{"get":{"operationId":"ClientAccountController_getConversations","summary":"Get my conversations","description":"The customer's messages grouped into threads — the inbox list view.\n\n#### Signature\n\n```http\nGET /client-data/conversations () -> Conversations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/messages`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Conversations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/conversations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/messages/{messageId}/status/{status}":{"put":{"operationId":"ClientAccountController_updateMessageStatus","summary":"Set a message status","description":"Marks one of the customer's messages read, archived or similar. Both values are path segments rather than a body.\n\n#### Signature\n\n```http\nPUT /client-data/messages/{messageId}/status/{status} (messageId: string, status: string) -> The updated message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/messages`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"messageId","required":true,"in":"path","description":"Message id.","schema":{"type":"string"},"example":"MSG-4821"},{"name":"status","required":true,"in":"path","description":"New status.","schema":{"type":"string"},"example":"read"}],"responses":{"200":{"description":"The updated message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/messages/{messageId}/status/{status}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/notifications":{"get":{"operationId":"ClientAccountController_getNotifications","summary":"Get my notifications","description":"Notifications for the signed-in customer.\n\n#### Signature\n\n```http\nGET /client-data/notifications (unreadOnly?: boolean) -> Notifications\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/notifications/push-token`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"unreadOnly","required":false,"in":"query","schema":{"type":"boolean"},"description":"true for unread notifications only."}],"responses":{"200":{"description":"Notifications","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/notifications","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/notifications/push-token":{"post":{"operationId":"ClientAccountController_savePushToken","summary":"Register a push token","description":"Registers a device for push notifications. Call it on every app launch — platform tokens rotate, and a stale one silently stops delivering.\n\n#### Signature\n\n```http\nPOST /client-data/notifications/push-token (body) -> The registered token\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/notifications`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The device token.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"fcm_dGhpcyBpcyBhIHRva2Vu","platform":"ios"}}}},"responses":{"201":{"description":"The registered token","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/notifications/push-token","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/payment-methods":{"get":{"operationId":"ClientAccountController_getPaymentMethods","summary":"Get my payment methods","description":"The customer's saved payment methods. Card details are masked — expect a last-four, never a full number.\n\n#### Signature\n\n```http\nGET /client-data/payment-methods () -> Payment methods\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/payment-methods`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Payment methods","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/payment-methods","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"post":{"operationId":"ClientAccountController_addPaymentMethod","summary":"Add a payment method","description":"Saves a payment method for the customer. Send a gateway token rather than raw card details — the card should reach the payment provider, not this API.\n\n#### Signature\n\n```http\nPOST /client-data/payment-methods (body) -> The saved method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Tokenise on the client. Raw card data here would pull the platform into PCI scope.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client-data/payment-methods/{paymentMethodId}/default`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The payment method.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"token":"pm_1PabcXYZ","gateway":"stripe","isDefault":true}}}},"responses":{"201":{"description":"The saved method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/payment-methods","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/payment-methods/{paymentMethodId}":{"delete":{"operationId":"ClientAccountController_removePaymentMethod","summary":"Remove a payment method","description":"Deletes a saved payment method. Any subscription billing against it will fail at its next renewal — check before removing the only method on file.\n\n#### Signature\n\n```http\nDELETE /client-data/payment-methods/{paymentMethodId} (paymentMethodId: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Recurring charges using it will start failing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/payment-methods`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"paymentMethodId","required":true,"in":"path","description":"Payment method id.","schema":{"type":"string"},"example":"PM-4821"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/payment-methods/{paymentMethodId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/payment-methods/{paymentMethodId}/default":{"put":{"operationId":"ClientAccountController_setDefaultPaymentMethod","summary":"Set the default payment method","description":"Makes one method the default for future purchases and renewals.\n\n#### Signature\n\n```http\nPUT /client-data/payment-methods/{paymentMethodId}/default (paymentMethodId: string) -> The updated method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/payment-methods`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"paymentMethodId","required":true,"in":"path","description":"Payment method id.","schema":{"type":"string"},"example":"PM-4821"}],"responses":{"200":{"description":"The updated method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/payment-methods/{paymentMethodId}/default","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/addresses":{"get":{"operationId":"ClientAccountController_getAddresses","summary":"Get my addresses","description":"The customer's saved addresses.\n\n#### Signature\n\n```http\nGET /client-data/addresses () -> Addresses\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/addresses`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Addresses","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/addresses","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"post":{"operationId":"ClientAccountController_saveAddress","summary":"Add an address","description":"Saves an address for the customer.\n\n#### Signature\n\n```http\nPOST /client-data/addresses (body) -> The saved address\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/addresses/autocomplete`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The address.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"line1":"12 Ada Way","city":"London","postcode":"E1 6AN","country":"GB","isDefault":true}}}},"responses":{"201":{"description":"The saved address","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/addresses","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/addresses/{addressId}":{"delete":{"operationId":"ClientAccountController_deleteAddress","summary":"Remove an address","description":"Deletes a saved address. Orders already placed keep the address they shipped to.\n\n#### Signature\n\n```http\nDELETE /client-data/addresses/{addressId} (addressId: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/addresses`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"addressId","required":true,"in":"path","description":"Address id.","schema":{"type":"string"},"example":"ADR-4821"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/addresses/{addressId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/addresses/autocomplete":{"post":{"operationId":"ClientAccountController_getAddressAutocomplete","summary":"Autocomplete an address","description":"Returns address suggestions as the customer types. Backed by a billable maps provider — debounce it rather than calling per keystroke.\n\n#### Signature\n\n```http\nPOST /client-data/addresses/autocomplete (body) -> Address suggestions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/addresses/place/{placeId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What has been typed.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"input":"12 Ada W","sessiontoken":"b1f2c3d4"}}}},"responses":{"201":{"description":"Address suggestions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/addresses/autocomplete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/addresses/place/{placeId}":{"get":{"operationId":"ClientAccountController_getPlaceDetails","summary":"Get address details for a place","description":"Expands a place id from autocomplete into a full structured address.\n\n#### Signature\n\n```http\nGET /client-data/addresses/place/{placeId} (placeId: string) -> The structured address\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `400` | PLACE_ID_REQUIRED | place_id is required | The place id is missing. | Take it from an autocomplete suggestion. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/addresses/autocomplete`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"placeId","required":true,"in":"path","description":"Place id from an autocomplete suggestion.","schema":{"type":"string"},"example":"ChIJd8BlQ2BZwokRAFUEcm_qrcA"}],"responses":{"200":{"description":"The structured address","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"place_id is required — The place id is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"place_id is required","path":"/client-data/addresses/place/{placeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/addresses/place/{placeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/wishlist":{"get":{"operationId":"ClientAccountController_getWishlist","summary":"Get my wishlist","description":"The customer's saved products.\n\n#### Signature\n\n```http\nGET /client-data/wishlist () -> Wishlist\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/wishlist/{productId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Wishlist","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/wishlist","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"post":{"operationId":"ClientAccountController_createWishlist","summary":"Create a wishlist","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The created wishlist","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/wishlist","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"],"description":"Creates a named wishlist, for customers keeping more than one list.\n\n#### Signature\n\n```http\nPOST /client-data/wishlist (body) -> The created wishlist\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client-data/wishlist-list/{wishlistId}`","requestBody":{"description":"The wishlist to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Christmas"}}}}}},"/client-data/wishlist/{productId}":{"post":{"operationId":"ClientAccountController_addToWishlist","summary":"Add a product to my wishlist","description":"Saves a product to the wishlist. The product must carry a SKU — that is what the wishlist keys on.\n\n#### Signature\n\n```http\nPOST /client-data/wishlist/{productId} (productId: string, body) -> The updated wishlist\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `400` | SKU_REQUIRED | product.sku is required | The product has no SKU. | Wishlist entries are keyed on SKU. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client-data/wishlist/{productId}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"productId","required":true,"in":"path","description":"Product id or SKU.","schema":{"type":"string"},"example":"DRK-COLA-330"}],"responses":{"201":{"description":"The updated wishlist","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"product.sku is required — The product has no SKU.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"product.sku is required","path":"/client-data/wishlist/{productId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/wishlist/{productId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"],"requestBody":{"description":"Optional product detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"product":{"sku":"DRK-COLA-330"}}}}}},"delete":{"operationId":"ClientAccountController_removeFromWishlist","summary":"Remove a product from my wishlist","description":"Removes a product from the wishlist.\n\n#### Signature\n\n```http\nDELETE /client-data/wishlist/{productId} (productId: string, wishlistId?: string) -> The updated wishlist\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `400` | SKU_REQUIRED | sku is required | No SKU was resolved from the path. | Supply the product SKU. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/wishlist`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"productId","required":true,"in":"path","description":"Product id or SKU.","schema":{"type":"string"},"example":"DRK-COLA-330"},{"name":"wishlistId","required":false,"in":"query","schema":{"type":"string"},"description":"A named wishlist; omit for the default one."}],"responses":{"200":{"description":"The updated wishlist","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"sku is required — No SKU was resolved from the path.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"sku is required","path":"/client-data/wishlist/{productId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/wishlist/{productId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/wishlist-list/{wishlistId}":{"delete":{"operationId":"ClientAccountController_deleteWishlist","summary":"Delete a wishlist","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"wishlistId","required":true,"in":"path","schema":{"type":"string"},"description":"Wishlist id.","example":"WL-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Not your wishlist — The wishlist belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Not your wishlist","path":"/client-data/wishlist-list/{wishlistId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/wishlist-list/{wishlistId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wishlist not found — No wishlist has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wishlist not found","path":"/client-data/wishlist-list/{wishlistId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"],"description":"Deletes one of the customer's named wishlists. Ownership is checked — deleting someone else's list is refused with `Not your wishlist`.\n\n#### Signature\n\n```http\nDELETE /client-data/wishlist-list/{wishlistId} (wishlistId: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `404` | WISHLIST_NOT_FOUND | Wishlist not found | No wishlist has that id. | Check the id. |\n| `400` | NOT_YOUR_WISHLIST | Not your wishlist | The wishlist belongs to another customer. | Ownership is enforced — you can only delete your own. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/wishlist`"}},"/client-data/all":{"get":{"operationId":"ClientAccountController_getAllClientData","summary":"Get everything about my account","description":"The customer's full account data in one response. Heavier than the dashboard — intended for a data-export or \"download my data\" flow rather than routine page loads.\n\n#### Signature\n\n```http\nGET /client-data/all () -> All account data\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Large response. Do not call it on every page.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/dashboard`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"All account data","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/all","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/transactions":{"get":{"operationId":"ClientAccountController_getTransactions","summary":"Get my transactions","description":"The customer's payment history across orders and subscriptions.\n\n#### Signature\n\n```http\nGET /client-data/transactions (page?: integer, limit?: integer, status?: string, paymentMethodId?: string) -> Transactions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/orders`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"paymentMethodId","required":false,"in":"query","schema":{"type":"string"},"description":"Only transactions paid with this saved method."},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Rows per page.","example":20},{"name":"status","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Transactions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/transactions","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/pay/{id}":{"get":{"operationId":"ClientAccountController_getPaymentLink","summary":"What a payment link is for, and what is owed","description":"Resolves the link's record and answers with its lines, total, what has been paid, the `balance` left, the payments so far and the gateways the org accepts (public fields only). A payment request is the payment itself, so it is either fully owed or settled.\n\n#### Signature\n\n```http\nGET /client-data/pay/{id} (id: string) -> The payment view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYMENT_NOT_FOUND | No payment matches that reference | The id resolves to neither a payment request nor an invoice. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/pay/{id}/intent`\n- `POST /client-data/pay/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"The record id the payment link carries: a payment-request pay token or transaction sk, or an invoice sk.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"200":{"description":"The payment view","content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["transaction","invoice"]},"id":{"type":"string"},"reference":{"type":"string","description":"Invoice number, or the request's ref."},"description":{"type":"string"},"requestedBy":{"type":"string","nullable":true,"description":"Payment requests only."},"issuedAt":{"type":"string","nullable":true},"dueAt":{"type":"string","nullable":true},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"quantity":{"type":"number"},"price":{"type":"number"},"discount":{"type":"number"},"amount":{"type":"number"}}}},"subTotal":{"type":"number"},"tax":{"type":"number"},"discount":{"type":"number"},"total":{"type":"number"},"totalPaid":{"type":"number","description":"For an invoice, summed from the payments recorded against it (refunds and voids subtracted) — never the stored amountPaid."},"balance":{"type":"number","description":"What is still owed. The only amount a payment against this link can charge."},"currency":{"type":"string","example":"USD"},"status":{"type":"string"},"payments":{"type":"array","items":{"type":"object","properties":{"amount":{"type":"number"},"currency":{"type":"string"},"gateway":{"type":"string"},"method":{"type":"string"},"status":{"type":"string"},"date":{"type":"string"}}}},"gateways":{"type":"array","items":{"type":"object","description":"What a payment page may see of a gateway: provider, label and its own public credential. Gateways missing a credential they need are left out.","properties":{"sk":{"type":"string"},"name":{"type":"string"},"data":{"type":"object","properties":{"provider":{"type":"string","example":"StripeProvider"},"name":{"type":"string"},"default":{"type":"boolean"},"publishableKey":{"type":"string"},"clientId":{"type":"string"},"sandbox":{"type":"boolean"},"testMode":{"type":"boolean"}}}}}},"customer":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No payment matches that reference — The id resolves to neither a payment request nor an invoice.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No payment matches that reference","path":"/client-data/pay/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account · Payment links"]},"post":{"operationId":"ClientAccountController_payPaymentLink","summary":"Record a payment taken against a payment link","description":"Records what the gateway charged. The amount is the **server** balance, not the caller's — with nothing owed nothing is recorded (`alreadySettled: true`, `paid: 0`), which is what stops a link collecting twice.\n\nAn invoice goes through the storefront payment path (transaction written, invoice updated, amount verified with the gateway); a payment request is settled in place rather than gaining a second row for the same reference. The payer is recorded: the signed-in customer, or the `email`/`name` given on the page.\n\n#### Signature\n\n```http\nPOST /client-data/pay/{id} (id: string, body) -> The refreshed payment view plus `paid` (what was recorded)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYMENT_NOT_FOUND | No payment matches that reference | The id resolves to neither a payment request nor an invoice. | — |\n| `400` | REF_REQUIRED | The gateway reference is required | Something is owed and no `ref` was sent. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/pay/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"The record id the payment link carries: a payment-request pay token or transaction sk, or an invoice sk.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","ref"],"properties":{"gateway":{"type":"string","description":"Gateway that took the money.","example":"stripe"},"ref":{"type":"string","description":"Gateway transaction / payment intent id.","example":"pi_3PabcXYZ"},"method":{"type":"string","example":"card"},"email":{"type":"string"},"name":{"type":"string"}}},"example":{"gateway":"stripe","ref":"pi_3PabcXYZ","method":"card","email":"ada@example.com"}}}},"responses":{"201":{"description":"The refreshed payment view plus `paid` (what was recorded)","content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["transaction","invoice"]},"id":{"type":"string"},"reference":{"type":"string","description":"Invoice number, or the request's ref."},"description":{"type":"string"},"requestedBy":{"type":"string","nullable":true,"description":"Payment requests only."},"issuedAt":{"type":"string","nullable":true},"dueAt":{"type":"string","nullable":true},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"quantity":{"type":"number"},"price":{"type":"number"},"discount":{"type":"number"},"amount":{"type":"number"}}}},"subTotal":{"type":"number"},"tax":{"type":"number"},"discount":{"type":"number"},"total":{"type":"number"},"totalPaid":{"type":"number","description":"For an invoice, summed from the payments recorded against it (refunds and voids subtracted) — never the stored amountPaid."},"balance":{"type":"number","description":"What is still owed. The only amount a payment against this link can charge."},"currency":{"type":"string","example":"USD"},"status":{"type":"string"},"payments":{"type":"array","items":{"type":"object","properties":{"amount":{"type":"number"},"currency":{"type":"string"},"gateway":{"type":"string"},"method":{"type":"string"},"status":{"type":"string"},"date":{"type":"string"}}}},"gateways":{"type":"array","items":{"type":"object","description":"What a payment page may see of a gateway: provider, label and its own public credential. Gateways missing a credential they need are left out.","properties":{"sk":{"type":"string"},"name":{"type":"string"},"data":{"type":"object","properties":{"provider":{"type":"string","example":"StripeProvider"},"name":{"type":"string"},"default":{"type":"boolean"},"publishableKey":{"type":"string"},"clientId":{"type":"string"},"sandbox":{"type":"boolean"},"testMode":{"type":"boolean"}}}}}},"customer":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}},"paid":{"type":"number"},"alreadySettled":{"type":"boolean"}}}}}},"400":{"description":"The gateway reference is required — Something is owed and no `ref` was sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The gateway reference is required","path":"/client-data/pay/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No payment matches that reference — The id resolves to neither a payment request nor an invoice.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No payment matches that reference","path":"/client-data/pay/{id}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account · Payment links"]}},"/client-data/pay/{id}/intent":{"post":{"operationId":"ClientAccountController_payPaymentLinkIntent","summary":"Start a card payment for a payment link","description":"Creates a Stripe PaymentIntent (card) for the **server's** balance on the record, so the browser cannot name the amount. The receipt email is the signed-in customer's, else `email`, else the one on the record. With nothing owed the answer is `{ alreadySettled: true }` and no intent is created.\n\n#### Signature\n\n```http\nPOST /client-data/pay/{id}/intent (id: string, body) -> The Stripe intent (client secret and publishable key), or { alreadySettled, balance, currency }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYMENT_NOT_FOUND | No payment matches that reference | The id resolves to neither a payment request nor an invoice. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/pay/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"The record id the payment link carries: a payment-request pay token or transaction sk, or an invoice sk.","schema":{"type":"string"},"example":"66f1a2b3c4d5e6f708192a3b"}],"responses":{"201":{"description":"The Stripe intent (client secret and publishable key), or { alreadySettled, balance, currency }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"alreadySettled":{"type":"boolean"},"balance":{"type":"number"},"currency":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No payment matches that reference — The id resolves to neither a payment request nor an invoice.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No payment matches that reference","path":"/client-data/pay/{id}/intent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account · Payment links"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Payer email when nobody is signed in."}}},"example":{"email":"ada@example.com"}}}}}},"/client-data/verification/status":{"get":{"operationId":"ClientAccountController_getVerificationStatus","summary":"Get my verification status","description":"Whether the customer's email and phone have been verified.\n\n#### Signature\n\n```http\nGET /client-data/verification/status () -> Verification status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/verification/email/send`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Verification status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/verification/status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/verification/email/send":{"post":{"operationId":"ClientAccountController_sendEmailVerification","summary":"Send an email verification code","description":"Sends a verification code to the customer's email. Rate-limit it — otherwise it doubles as a way to send mail to an address repeatedly.\n\n#### Signature\n\n```http\nPOST /client-data/verification/email/send (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/verification/email/verify`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/verification/email/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"],"requestBody":{"description":"Optional target.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/client-data/verification/email/verify":{"post":{"operationId":"ClientAccountController_verifyEmail","summary":"Verify an email code","description":"Confirms the code sent to the customer's email, marking it verified.\n\n#### Signature\n\n```http\nPOST /client-data/verification/email/verify (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/verification/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The code.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"481625"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/verification/email/verify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/verification/phone/send":{"post":{"operationId":"ClientAccountController_sendPhoneVerification","summary":"Send a phone verification code","description":"Sends a verification code by SMS. Each send costs money and lands on someone's phone — rate-limit per number, not just per account.\n\n#### Signature\n\n```http\nPOST /client-data/verification/phone/send (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/verification/phone/verify`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The number to verify.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"phone":"+15551234567"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/verification/phone/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/verification/phone/verify":{"post":{"operationId":"ClientAccountController_verifyPhone","summary":"Verify a phone code","description":"Confirms the SMS code, marking the number verified.\n\n#### Signature\n\n```http\nPOST /client-data/verification/phone/verify (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/verification/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The code.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"481625"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/verification/phone/verify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/analytics":{"get":{"operationId":"ClientAccountController_getAnalytics","summary":"Get my account analytics","description":"Summary figures about the customer's own activity — spend, order count, engagement.\n\n#### Signature\n\n```http\nGET /client-data/analytics () -> Account analytics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/dashboard`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Account analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/analytics","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/expert/messages":{"post":{"operationId":"ClientAccountController_sendMessageToExpert","summary":"Message an expert","description":"Sends a message to an expert.\n\n#### Signature\n\n```http\nPOST /client-data/expert/messages (send?: boolean, body) -> The sent message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/expert/hire`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"send","required":false,"in":"query","schema":{"type":"boolean"},"description":"true to send immediately; otherwise saved as a draft."}],"requestBody":{"description":"The message.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"expertId":"EXP-4821","message":"Can you advise on sizing?"}}}},"responses":{"201":{"description":"The sent message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/expert/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]},"get":{"operationId":"ClientAccountController_getExpertMessages","summary":"Get my expert messages","description":"Messages exchanged with experts.\n\n#### Signature\n\n```http\nGET /client-data/expert/messages (page?: integer, pageSize?: integer, sort?: string, sortType?: string, conversationWith?: string) -> Expert messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/expert/messages`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"conversationWith","required":false,"in":"query","schema":{"type":"string"},"description":"Only the thread with this address."},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"sort","required":false,"in":"query","schema":{"type":"string"},"description":"Field to sort by."},{"name":"sortType","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"Expert messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/expert/messages","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/expert/hire":{"post":{"operationId":"ClientAccountController_hireExpert","summary":"Hire an expert","description":"Engages an expert. This commits the customer to a paid engagement, so confirm terms before calling it.\n\n#### Signature\n\n```http\nPOST /client-data/expert/hire (body) -> The engagement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/expert/messages`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The engagement.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"expertId":"EXP-4821","scope":"One-hour consultation"}}}},"responses":{"201":{"description":"The engagement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/expert/hire","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/files":{"get":{"operationId":"ClientAccountController_getFiles","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Files","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/files","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"],"summary":"Get my files","description":"Files the customer has uploaded, in their own storage area.\n\n#### Signature\n\n```http\nGET /client-data/files () -> Files\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/files/upload`"}},"/client-data/files/{path}":{"delete":{"operationId":"ClientAccountController_deleteFile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"path","required":true,"in":"path","schema":{"type":"string"},"description":"File path within the customer's area. URL-encode any slashes.","example":"documents%2Fcontract.pdf"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/files/{path}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"],"summary":"Delete a file","description":"Deletes one of the customer's files. The path is a segment, so it must be URL-encoded if it contains slashes.\n\n#### Signature\n\n```http\nDELETE /client-data/files/{path} (path: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/files`"}},"/client-data/files/upload":{"post":{"operationId":"ClientAccountController_upload","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The uploaded file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/files/upload","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"],"summary":"Upload a file","description":"Uploads a file to the customer's own storage area.\n\n#### Signature\n\n```http\nPOST /client-data/files/upload (body) -> The uploaded file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client-data/files/{path}`","requestBody":{"description":"Multipart form with the file.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/client-data/benefits":{"get":{"operationId":"ClientAccountController_getCustomerBenefits","summary":"Get available benefits","description":"Benefits the customer can enrol in.\n\n#### Signature\n\n```http\nGET /client-data/benefits () -> Available benefits\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/benefits/enroll`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Available benefits","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/benefits","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/benefits/enroll":{"post":{"operationId":"ClientAccountController_applyForBenefit","summary":"Enrol in a benefit","description":"Enrols the customer in a benefit. Enrolment may require an application form and a legal agreement — the CRM benefit endpoints enforce those rules.\n\n#### Signature\n\n```http\nPOST /client-data/benefits/enroll (body) -> The enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client-data/benefits/enrollments`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The enrolment.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"benefit":"health-plan","agreementAccepted":true}}}},"responses":{"201":{"description":"The enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/benefits/enroll","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/client-data/benefits/enrollments":{"get":{"operationId":"ClientAccountController_getMyEnrollments","summary":"Get my benefit enrolments","description":"The customer's benefit enrolments and their status.\n\n#### Signature\n\n```http\nGET /client-data/benefits/enrollments () -> Enrolments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | CUSTOMER_REQUIRED | Customer required | No signed-in customer could be resolved. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client-data/benefits/enroll`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Enrolments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Customer required — No signed-in customer could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Customer required","path":"/client-data/benefits/enrollments","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Client Account"]}},"/sync":{"get":{"operationId":"SyncController_syncInfo","summary":"Get sync information","description":"Overall sync state for the platform.\n\n#### Signature\n\n```http\nGET /sync () -> Sync information\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/info`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Sync information","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/info":{"get":{"operationId":"SyncController_info","summary":"Get organization sync info","description":"Sync configuration and last-run detail for the calling org.\n\n#### Signature\n\n```http\nGET /sync/info () -> Org sync info\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Org sync info","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/purge":{"get":{"operationId":"SyncController_purge","summary":"Purge sync data","description":"Deletes the org's accumulated sync data.\n\n**This is a destructive operation exposed as a GET.** Anything that pre-fetches links, a browser address bar, or a crawler following a URL will trigger it. There is no confirmation body and no undo — the next sync has to rebuild everything from the source platform.\n\n#### Signature\n\n```http\nGET /sync/purge () -> The purge result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Destructive, and reachable by a plain GET.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sync/trigger`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The purge result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/queue":{"post":{"operationId":"SyncController_queueJob","summary":"Queue a sync job","description":"Queues a sync job for processing. The body is the job configuration; an empty body is rejected.\n\n#### Signature\n\n```http\nPOST /sync/queue (body) -> The queued job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMPTY_DATA | Empty Data | The body is empty. | Supply a job configuration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sync/trigger`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The sync job configuration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"platform":"shopify","syncType":"products"}}}},"responses":{"201":{"description":"The queued job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Empty Data — The body is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Empty Data","path":"/sync/queue","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/pause/{name}":{"get":{"operationId":"SyncController_pause","summary":"Pause a sync job","description":"Pauses a running sync job. **A state change exposed as a GET** — treat the URL as an action, not something safe to prefetch.\n\nPausing stops new work; a job already mid-run finishes what it is doing.\n\n#### Signature\n\n```http\nGET /sync/pause/{name} (name: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Mutating GET.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/resume/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Sync job name.","schema":{"type":"string"},"example":"shopify-products"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/resume/{name}":{"get":{"operationId":"SyncController_resume","summary":"Resume a sync job","description":"Resumes a paused sync job. Also a mutating GET.\n\n#### Signature\n\n```http\nGET /sync/resume/{name} (name: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Mutating GET.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/pause/{name}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"name","required":true,"in":"path","description":"Sync job name.","schema":{"type":"string"},"example":"shopify-products"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/trigger":{"post":{"operationId":"SyncController_triggerSync","summary":"Trigger a sync","description":"Manually runs a sync for a platform. `syncType` takes a single type **or an array** of types, so several can be triggered together.\n\n`options.force` overrides the guard that stops a sync starting while one is already running — which is exactly the guard that prevents two concurrent syncs writing over each other. Use it only when you know the previous run is genuinely dead.\n\nWithout `startDate`/`endDate` the platform's own default window applies; supply them to back-fill.\n\n#### Signature\n\n```http\nPOST /sync/trigger (body) -> The triggered job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `force` bypasses the concurrent-run guard.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/platforms`\n- `GET /sync/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What to sync.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["platform","syncType"],"properties":{"platform":{"type":"string","description":"Platform to sync.","example":"shopify"},"syncType":{"description":"One type, or several.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"example":"products"},"options":{"type":"object","properties":{"startDate":{"type":"string","description":"ISO string.","example":"2026-08-01"},"endDate":{"type":"string","description":"ISO string.","example":"2026-08-31"},"force":{"type":"boolean","description":"Run even if a sync is already in flight.","example":false}}}}},"examples":{"single":{"summary":"One sync type","value":{"platform":"shopify","syncType":"products"}},"backfill":{"summary":"Back-fill a date range across several types","value":{"platform":"shopify","syncType":["products","orders"],"options":{"startDate":"2026-08-01","endDate":"2026-08-31"}}}}}}},"responses":{"200":{"description":"Sync job triggered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"platform":{"type":"string"},"syncType":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"orgId":{"type":"string"},"jobId":{"type":"string"},"timestamp":{"type":"number"}}}}}},"201":{"description":"The triggered job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid sync type or missing parameters"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/trigger/{platform}/{syncType}":{"post":{"operationId":"SyncController_triggerSyncByParam","summary":"Trigger a sync by URL","description":"The same trigger, with the platform and sync type in the path instead of the body — convenient from a script or a webhook. `syncType` is optional; omitting it runs the platform's default set.\n\n#### Signature\n\n```http\nPOST /sync/trigger/{platform}/{syncType} (platform: string, syncType: string, body) -> The triggered job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sync/trigger`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"platform","required":true,"in":"path","description":"Platform to sync.","schema":{"type":"string"},"example":"shopify"},{"name":"syncType","required":true,"in":"path","description":"Sync type. Optional — omit for the platform default.","schema":{"type":"string"},"example":"products"}],"requestBody":{"description":"Optional options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"force":false}}}},"responses":{"201":{"description":"The triggered job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid sync type"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/platforms":{"get":{"operationId":"SyncController_getAvailablePlatforms","summary":"List platforms and sync types","description":"Which platforms can be synced and which sync types each supports — read this before constructing a trigger, since the valid types differ per platform.\n\n#### Signature\n\n```http\nGET /sync/platforms () -> Platforms and their sync types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /sync/trigger`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Platforms and their sync types","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/status":{"get":{"operationId":"SyncController_getSyncStatus","summary":"Get sync job status","description":"The state of the org's sync jobs — what is running, what finished, what failed.\n\n#### Signature\n\n```http\nGET /sync/status () -> Sync status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/status/{platform}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Sync status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/sync/status/{platform}":{"get":{"operationId":"SyncController_getPlatformSyncStatus","summary":"Get one platform's sync status","description":"Sync state for a single platform.\n\n#### Signature\n\n```http\nGET /sync/status/{platform} (platform: string) -> Platform sync status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /sync/status`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"platform","required":true,"in":"path","description":"Platform name.","schema":{"type":"string"},"example":"shopify"}],"responses":{"200":{"description":"Platform sync status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Sync"]}},"/notice/feed":{"get":{"operationId":"NoticeController_feed","summary":"Get notices for this viewer","description":"The notices to show the current viewer right now — targeting and frequency rules are applied server-side, so a client shows what it is given rather than deciding itself. `surface` says where they will be displayed.\n\n#### Signature\n\n```http\nGET /notice/feed (site?: string, surface?: string) -> Notices to display\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /notice/{id}/display`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"site","required":false,"in":"query","schema":{"type":"string"},"example":"acme-shop"},{"name":"surface","required":false,"in":"query","schema":{"type":"string"},"description":"Where the notices will be shown.","example":"dashboard"}],"responses":{"200":{"description":"Notices to display","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/inbox":{"get":{"operationId":"NoticeController_inbox","summary":"Get the notice inbox","description":"Every notice the org has been sent, whether or not it is currently eligible to display — the archive, where `feed` is the live selection.\n\n#### Signature\n\n```http\nGET /notice/inbox (site?: string) -> Notices\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /notice/feed`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"site","required":false,"in":"query","schema":{"type":"string"},"example":"acme-shop"}],"responses":{"200":{"description":"Notices","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/{id}/display":{"post":{"operationId":"NoticeController_display","summary":"Record a notice display","description":"Records that a notice was actually shown. This feeds frequency capping as well as reporting — without it, a notice capped at three impressions will keep reappearing.\n\n#### Signature\n\n```http\nPOST /notice/{id}/display (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Drives frequency capping, not just analytics.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /notice/{id}/click`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Notice id.","schema":{"type":"string"},"example":"NTC-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/{id}/click":{"post":{"operationId":"NoticeController_click","summary":"Record a notice click","description":"Records a click on one of a notice's actions.\n\n#### Signature\n\n```http\nPOST /notice/{id}/click (id: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /notice/{id}/insights`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Notice id.","schema":{"type":"string"},"example":"NTC-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"],"requestBody":{"description":"Which action.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"action":"learn-more"}}}}}},"/notice/{id}/accept":{"post":{"operationId":"NoticeController_accept","summary":"Accept a notice","description":"Records that the viewer accepted what the notice asked. For a notice presenting terms, this is the acceptance record — it carries weight beyond analytics.\n\n#### Signature\n\n```http\nPOST /notice/{id}/accept (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /notice/{id}/reject`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Notice id.","schema":{"type":"string"},"example":"NTC-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/{id}/reject":{"post":{"operationId":"NoticeController_reject","summary":"Reject a notice","description":"Records that the viewer declined. Distinct from dismissing: rejection is an answer, dismissal is closing the box.\n\n#### Signature\n\n```http\nPOST /notice/{id}/reject (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /notice/{id}/dismiss`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Notice id.","schema":{"type":"string"},"example":"NTC-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/{id}/dismiss":{"post":{"operationId":"NoticeController_dismiss","summary":"Dismiss a notice","description":"Closes a notice for this viewer without answering it. Typically stops it reappearing for that viewer.\n\n#### Signature\n\n```http\nPOST /notice/{id}/dismiss (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /notice/{id}/reject`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Notice id.","schema":{"type":"string"},"example":"NTC-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/activity":{"get":{"operationId":"NoticeController_activity","summary":"Get the notice activity log","description":"Raw notice events — displays, clicks, acceptances — filterable by notice, event type and viewer.\n\n#### Signature\n\n```http\nGET /notice/activity (noticeId?: string, event?: string, viewerId?: string, page?: integer, pageSize?: integer) -> Activity events\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /notice/insights`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"noticeId","required":false,"in":"query","schema":{"type":"string"},"example":"NTC-4821"},{"name":"event","required":false,"in":"query","schema":{"type":"string"},"example":"click"},{"name":"viewerId","required":false,"in":"query","schema":{"type":"string"},"example":"CUST-4821"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Activity events","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/insights":{"get":{"operationId":"NoticeController_overview","summary":"Get notice engagement","description":"Engagement across every live notice — which are being seen and acted on.\n\n#### Signature\n\n```http\nGET /notice/insights (site?: string) -> Engagement figures\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /notice/{id}/insights`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"site","required":false,"in":"query","schema":{"type":"string"},"example":"acme-shop"}],"responses":{"200":{"description":"Engagement figures","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/notice/{id}/insights":{"get":{"operationId":"NoticeController_insights","summary":"Get one notice's insights","description":"The funnel for a single notice — displayed, clicked, accepted — plus its recent activity.\n\n#### Signature\n\n```http\nGET /notice/{id}/insights (id: string) -> Funnel and activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /notice/activity`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Notice id.","schema":{"type":"string"},"example":"NTC-4821"},{"name":"limit","required":false,"in":"query","description":"Recent interactions to return (default 50)","schema":{"type":"string"}}],"responses":{"200":{"description":"Funnel and activity","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notices"]}},"/monitoring/overview":{"get":{"operationId":"MonitoringController_getSystemOverview","summary":"Get the system overview","parameters":[],"responses":{"200":{"description":"The overview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"The operational dashboard in one call — health, resources, queues and recent alerts together. The first thing to look at when something is wrong.\n\n#### Signature\n\n```http\nGET /monitoring/overview () -> The overview\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/health`"}},"/monitoring/historical":{"get":{"operationId":"MonitoringController_getHistoricalMetrics","summary":"Get historical metrics","parameters":[{"name":"range","required":false,"in":"query","description":"Time range, e.g. `1h`, `24h`, `7d`.","schema":{"type":"string"},"example":"24h"}],"responses":{"200":{"description":"Historical metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Metrics over a time range, for spotting a trend rather than a moment. `range` names the window.\n\n#### Signature\n\n```http\nGET /monitoring/historical (range?: string) -> Historical metrics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/system-metrics`"}},"/monitoring/health":{"get":{"operationId":"MonitoringController_getHealthCheck","summary":"Get detailed health checks","parameters":[],"responses":{"200":{"description":"Component health","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Per-component health for every system dependency — database, Redis, queues, external services. More detailed than a liveness probe: a component can be degraded while the process is alive.\n\nThis path is exempt from rate limiting, so it is safe to poll from a monitor.\n\n#### Signature\n\n```http\nGET /monitoring/health () -> Component health\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/system-metrics`"}},"/monitoring/system-metrics":{"get":{"operationId":"MonitoringController_getSystemMetrics","summary":"Get system resource metrics","parameters":[],"responses":{"200":{"description":"Resource metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"CPU, memory and process metrics for the running instance.\n\n#### Signature\n\n```http\nGET /monitoring/system-metrics () -> Resource metrics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/historical`"}},"/monitoring/queues":{"get":{"operationId":"MonitoringController_getQueueStats","summary":"Get queue statistics","parameters":[],"responses":{"200":{"description":"Queue statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Job counts and processing rates across queues. Rising waiting counts with a flat completed rate is the signature of a stalled worker.\n\n#### Signature\n\n```http\nGET /monitoring/queues () -> Queue statistics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/queues/{queueName}`"}},"/monitoring/queues/{queueName}":{"get":{"operationId":"MonitoringController_getQueueDetails","summary":"Get one queue's statistics","parameters":[{"name":"queueName","required":true,"in":"path","description":"Queue name.","schema":{"type":"string"},"example":"sync"}],"responses":{"200":{"description":"Queue statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Statistics for a single queue.\n\n#### Signature\n\n```http\nGET /monitoring/queues/{queueName} (queueName: string) -> Queue statistics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/queues`"}},"/monitoring/alerts":{"get":{"operationId":"MonitoringController_getRecentAlerts","summary":"Get recent alerts","parameters":[{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Alerts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Alerts and notifications the system has raised, newest first.\n\n#### Signature\n\n```http\nGET /monitoring/alerts (limit?: integer) -> Alerts\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/alert-notifications`"}},"/monitoring/user-activity":{"get":{"operationId":"MonitoringController_getUserActivity","summary":"Get user activity metrics","parameters":[],"responses":{"200":{"description":"Activity metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Current user activity across the platform — sessions and request volume.\n\n#### Signature\n\n```http\nGET /monitoring/user-activity () -> Activity metrics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n- Reads across organizations, not just the calling one.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/web-activity`"}},"/monitoring/company-creation":{"get":{"operationId":"MonitoringController_getCompanyCreationMetrics","summary":"Monitor company creation","parameters":[],"responses":{"200":{"description":"Recent company creations","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"New organization registrations recorded in the **root org** — a growth signal for operators.\n\nBecause it reads root-org records on an unauthenticated route, it discloses who has recently signed up to anyone who can reach it.\n\n#### Signature\n\n```http\nGET /monitoring/company-creation () -> Recent company creations\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n- Reads across organizations, not just the calling one.\n- Discloses recent signups.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/domain-mappings`"}},"/monitoring/domain-mappings":{"get":{"operationId":"MonitoringController_getDomainMappingMetrics","summary":"Monitor domain mappings","parameters":[],"responses":{"200":{"description":"Domain mappings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Domain mappings recorded in the root org — which hostnames route where across the platform. Cross-org, and unauthenticated: it maps customers to their domains.\n\n#### Signature\n\n```http\nGET /monitoring/domain-mappings () -> Domain mappings\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n- Reads across organizations, not just the calling one.\n- Discloses customer domains.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/company-creation`"}},"/monitoring/alert-notifications":{"get":{"operationId":"MonitoringController_getAlertNotificationMetrics","summary":"Monitor alerts and notifications","parameters":[],"responses":{"200":{"description":"Alert notifications","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Alerts and notifications recorded in the shared and root orgs — the platform-wide notification stream.\n\n#### Signature\n\n```http\nGET /monitoring/alert-notifications () -> Alert notifications\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n- Reads across organizations, not just the calling one.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/alerts`"}},"/monitoring/usage":{"get":{"operationId":"MonitoringController_getUsageMetrics","summary":"Monitor platform usage","parameters":[],"responses":{"200":{"description":"Usage statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Platform usage and API consumption figures across organizations.\n\n#### Signature\n\n```http\nGET /monitoring/usage () -> Usage statistics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n- Reads across organizations, not just the calling one.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/overview`"}},"/monitoring/web-activity":{"get":{"operationId":"MonitoringController_getWebActivityMetrics","summary":"Monitor web activity","parameters":[],"responses":{"200":{"description":"Web activity","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Monitoring"],"description":"Site visits and user interactions recorded across the platform.\n\n#### Signature\n\n```http\nGET /monitoring/web-activity () -> Web activity\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n- Reads across organizations, not just the calling one.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /monitoring/user-activity`"}},"/analytics/dashboard/all":{"get":{"operationId":"AnalyticsController_getAllDashboardData","summary":"Get all dashboard data","description":"Every dashboard in one response — website, blog, workflow, storefront, tickets, leads, automation, social and users. Heavier than fetching one, but it saves nine round trips when a page shows them together.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/all (startDate?: string, endDate?: string) -> All dashboards\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/website`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"All dashboards","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/website":{"get":{"operationId":"AnalyticsController_getWebsiteAnalytics","summary":"Get website analytics","description":"Traffic and engagement for the org's sites. Narrow it with `siteName`, `domain` or `host` — without one, the figures cover every site the org runs, which is rarely what a per-site view wants.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/website (startDate?: string, endDate?: string, siteName?: string, domain?: string, host?: string) -> Website analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/filter-options`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"},{"name":"siteName","required":false,"in":"query","schema":{"type":"string"},"example":"acme-shop"},{"name":"domain","required":false,"in":"query","schema":{"type":"string"},"example":"shop.example.com"},{"name":"host","required":false,"in":"query","schema":{"type":"string"},"example":"shop.example.com"}],"responses":{"200":{"description":"Website analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/blog":{"get":{"operationId":"AnalyticsController_getBlogAnalytics","summary":"Get blog analytics","description":"Post performance and readership over a date range.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/blog (startDate?: string, endDate?: string) -> Blog analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/all`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Blog analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/workflow":{"get":{"operationId":"AnalyticsController_getWorkflowAnalytics","summary":"Get workflow analytics","description":"Pipeline throughput and stage timings, drawn from the workflow engine.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/workflow (startDate?: string, endDate?: string) -> Workflow analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /workflow/analytics/{workflowId}/wait-times`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Workflow analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/storefront":{"get":{"operationId":"AnalyticsController_getStorefrontAnalytics","summary":"Get storefront analytics","description":"Orders, revenue and conversion for the storefront.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/storefront (startDate?: string, endDate?: string) -> Storefront analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/all`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Storefront analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/tickets":{"get":{"operationId":"AnalyticsController_getTicketAnalytics","summary":"Get ticket analytics","description":"Support ticket volume, resolution time and backlog.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/tickets (startDate?: string, endDate?: string) -> Ticket analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/all`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Ticket analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/leads":{"get":{"operationId":"AnalyticsController_getLeadsAnalytics","summary":"Get leads analytics","description":"Lead volume, source and conversion over a date range.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/leads (startDate?: string, endDate?: string) -> Leads analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/automation`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Leads analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/automation":{"get":{"operationId":"AnalyticsController_getAutomationAnalytics","summary":"Get automation analytics","description":"Automation run counts, successes and failures.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/automation (startDate?: string, endDate?: string) -> Automation analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/all`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Automation analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/social":{"get":{"operationId":"AnalyticsController_getSocialMediaAnalytics","summary":"Get social media analytics","description":"Social reach and engagement across connected accounts.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/social (startDate?: string, endDate?: string) -> Social analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/all`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"Social analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/users":{"get":{"operationId":"AnalyticsController_getUserAccountAnalytics","summary":"Get user account analytics","description":"Account signups, activity and retention for the org.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/users (startDate?: string, endDate?: string) -> User analytics\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/all`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"User analytics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/analytics/dashboard/{dashboardType}/refresh":{"get":{"operationId":"AnalyticsController_refreshDashboard","summary":"Refresh a dashboard","description":"Recomputes one dashboard rather than serving the cached figures. Deliberately more expensive than the plain read — call it when the numbers must be current, not on every page load.\n\n#### Signature\n\n```http\nGET /analytics/dashboard/{dashboardType}/refresh (dashboardType: string, startDate?: string, endDate?: string) -> The refreshed dashboard\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — this controller is entirely public.\n- Recomputes rather than reading cache.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /analytics/dashboard/all`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"dashboardType","required":true,"in":"path","description":"Which dashboard — `website`, `blog`, `storefront`, and so on.","schema":{"type":"string"},"example":"storefront"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-31"}],"responses":{"200":{"description":"The refreshed dashboard","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Analytics"]}},"/documentation":{"get":{"operationId":"DocumentationController_page","parameters":[],"responses":{"200":{"description":"HTML page","content":{"text/html":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"The API reference page","description":"The reference for people: a page that loads the index and fetches one section at a time. It needs JavaScript; crawlers and agents should use the markdown and search routes instead.\n\n#### Signature\n\n```http\nGET /documentation () -> HTML page\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /llms.txt`\n- `GET /documentation.md`","tags":["API reference"]}},"/api-docs":{"get":{"operationId":"DocumentationController_alias","parameters":[],"responses":{"302":{"description":"Redirect to /documentation","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Redirect /api-docs to the reference","description":"A conventional alias, so a reasonable guess lands somewhere useful: 302 to `/documentation`.\n\n#### Signature\n\n```http\nGET /api-docs () -> Redirect to /documentation\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /documentation`","tags":["API reference"]}},"/docs":{"get":{"operationId":"DocumentationController_alias","parameters":[],"responses":{"302":{"description":"Redirect to /documentation","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Redirect /docs to the reference","description":"A conventional alias, so a reasonable guess lands somewhere useful: 302 to `/documentation`.\n\n#### Signature\n\n```http\nGET /docs () -> Redirect to /documentation\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /documentation`","tags":["API reference"]}},"/swagger":{"get":{"operationId":"DocumentationController_alias","parameters":[],"responses":{"302":{"description":"Redirect to /documentation","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Redirect /swagger to the reference","description":"A conventional alias, so a reasonable guess lands somewhere useful: 302 to `/documentation`.\n\n#### Signature\n\n```http\nGET /swagger () -> Redirect to /documentation\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /documentation`","tags":["API reference"]}},"/llms.txt":{"get":{"operationId":"DocumentationController_llms","parameters":[],"responses":{"200":{"description":"Plain text","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"API documentation is not ready yet — The server has just started and has not built the document yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"API documentation is not ready yet","path":"/llms.txt","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"What this API is, for agents","description":"The entry point an agent checks before guessing URLs (llmstxt.org): what the platform is and where the rest of the reference lives — a map to the real files rather than a copy of them. Cached for 5 minutes.\n\n#### Signature\n\n```http\nGET /llms.txt () -> Plain text\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | DOCS_NOT_READY | API documentation is not ready yet | The server has just started and has not built the document yet. | Retry in a few seconds. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /documentation/search`\n- `GET /documentation.md`","tags":["API reference"]}},"/documentation.md":{"get":{"operationId":"DocumentationController_indexMarkdown","parameters":[],"responses":{"200":{"description":"Markdown","content":{"text/markdown":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"API documentation is not ready yet — The server has just started and has not built the document yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"API documentation is not ready yet","path":"/documentation.md","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Every endpoint, one line each","description":"The whole list as markdown, grouped by section — for reading breadth-first. Cached for 5 minutes.\n\n#### Signature\n\n```http\nGET /documentation.md () -> Markdown\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | DOCS_NOT_READY | API documentation is not ready yet | The server has just started and has not built the document yet. | Retry in a few seconds. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /documentation/tag/{slug}.md`","tags":["API reference"]}},"/documentation/tag/{slug}.md":{"get":{"operationId":"DocumentationController_tagMarkdown","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"},"description":"Section slug — the tag name lowercased with dashes, as listed in `/documentation.md` or `/documentation/index.json`.","example":"ai-employees"}],"responses":{"200":{"description":"Markdown","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No documented endpoints for \"ai-employees\" — No section has that slug.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No documented endpoints for \"ai-employees\"","path":"/documentation/tag/{slug}.md","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"API documentation is not ready yet — The server has just started and has not built the document yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"API documentation is not ready yet","path":"/documentation/tag/{slug}.md","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"One section in full, as markdown","description":"Every endpoint in one section with its parameters, bodies, responses, errors and examples.\n\n#### Signature\n\n```http\nGET /documentation/tag/{slug}.md (slug: string) -> Markdown\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SECTION_NOT_FOUND | No documented endpoints for \"ai-employees\" | No section has that slug. | — |\n| `503` | DOCS_NOT_READY | API documentation is not ready yet | The server has just started and has not built the document yet. | Retry in a few seconds. |\n\nPlus the standard platform errors: `429`, `500`.","tags":["API reference"]}},"/documentation/search":{"get":{"operationId":"DocumentationController_search","parameters":[{"name":"q","required":true,"in":"query","schema":{"type":"string"},"example":"refund"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","default":20},"description":"Up to 100."}],"responses":{"200":{"description":"{ query, count, results }","content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"count":{"type":"integer"},"results":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"method":{"type":"string"},"path":{"type":"string"},"summary":{"type":"string"},"group":{"type":"string"},"detail":{"type":"string","description":"URL of GET /documentation/endpoint for it."},"section":{"type":"string","description":"URL of its section's markdown."}}}}}}}}},"400":{"description":"Pass ?q= to search, e.g. /documentation/search?q=refund — `q` is missing or empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pass ?q= to search, e.g. /documentation/search?q=refund","path":"/documentation/search","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Find endpoints by phrase","description":"The endpoints that match a phrase, best first, each with the URL of its full detail and of its section — so a caller never has to construct one.\n\n#### Signature\n\n```http\nGET /documentation/search (q?: string, limit?: integer) -> { query, count, results }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | QUERY_REQUIRED | Pass ?q= to search, e.g. /documentation/search?q=refund | `q` is missing or empty. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /documentation/endpoint`","tags":["API reference"]}},"/documentation/endpoint":{"get":{"operationId":"DocumentationController_endpoint","parameters":[{"name":"method","required":true,"in":"query","schema":{"type":"string"},"example":"GET"},{"name":"path","required":true,"in":"query","schema":{"type":"string"},"description":"OpenAPI-style path with `{param}` placeholders.","example":"/repository/find/{datatype}/{dataId}"},{"name":"format","required":false,"in":"query","schema":{"type":"string","enum":["md","json"],"default":"md"}}],"responses":{"200":{"description":"Markdown, or `{ method, path, tag, operation, components }` with format=json","content":{"text/markdown":{"schema":{"type":"string"}}}},"400":{"description":"Pass ?method= and ?path=, e.g. /documentation/endpoint?method=GET&path=/repository/find/{datatype}/{dataId} — `method` or `path` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pass ?method= and ?path=, e.g. /documentation/endpoint?method=GET&path=/repository/find/{datatype}/{dataId}","path":"/documentation/endpoint","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No documented endpoint for GET /nope — No documented operation matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No documented endpoint for GET /nope","path":"/documentation/endpoint","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"One endpoint in full","description":"One endpoint, addressed by method and path (operation ids are not unique across modules). Markdown by default; `format=json` returns the OpenAPI operation with the components it uses.\n\n#### Signature\n\n```http\nGET /documentation/endpoint (method?: string, path?: string, format?: string) -> Markdown, or `{ method, path, tag, operation, components }` with format=json\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | METHOD_AND_PATH_REQUIRED | Pass ?method= and ?path=, e.g. /documentation/endpoint?method=GET&path=/repository/find/{datatype}/{dataId} | `method` or `path` is missing. | — |\n| `404` | ENDPOINT_NOT_FOUND | No documented endpoint for GET /nope | No documented operation matches. | — |\n\nPlus the standard platform errors: `429`, `500`.","tags":["API reference"]}},"/documentation-json":{"get":{"operationId":"DocumentationController_json","parameters":[],"responses":{"200":{"description":"OpenAPI 3 document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"API documentation is not ready yet — The server has just started and has not built the document yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"API documentation is not ready yet","path":"/documentation-json","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"The OpenAPI document","description":"The complete OpenAPI 3 document, for code generators and Postman. Large — the page does not load it. Same as `/openapi.json`. Cached for 5 minutes.\n\n#### Signature\n\n```http\nGET /documentation-json () -> OpenAPI 3 document\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | DOCS_NOT_READY | API documentation is not ready yet | The server has just started and has not built the document yet. | Retry in a few seconds. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /openapi.json`","tags":["API reference"]}},"/openapi.json":{"get":{"operationId":"DocumentationController_json","parameters":[],"responses":{"200":{"description":"OpenAPI 3 document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"API documentation is not ready yet — The server has just started and has not built the document yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"API documentation is not ready yet","path":"/openapi.json","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"The OpenAPI document","description":"The complete OpenAPI 3 document, for code generators and Postman. Same as `/documentation-json`. Cached for 5 minutes.\n\n#### Signature\n\n```http\nGET /openapi.json () -> OpenAPI 3 document\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | DOCS_NOT_READY | API documentation is not ready yet | The server has just started and has not built the document yet. | Retry in a few seconds. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /documentation-json`","tags":["API reference"]}},"/documentation/index.json":{"get":{"operationId":"DocumentationController_indexJson","parameters":[],"responses":{"200":{"description":"Index","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"groups":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string"},"slug":{"type":"string"}}}}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"API documentation is not ready yet — The server has just started and has not built the document yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"API documentation is not ready yet","path":"/documentation/index.json","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"The reference's navigation index","description":"The sections and a line per endpoint — what the reference page loads before it draws anything. Cached for 5 minutes.\n\n#### Signature\n\n```http\nGET /documentation/index.json () -> Index\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | DOCS_NOT_READY | API documentation is not ready yet | The server has just started and has not built the document yet. | Retry in a few seconds. |\n\nPlus the standard platform errors: `429`, `500`.","tags":["API reference"]}},"/documentation/tag/{slug}.json":{"get":{"operationId":"DocumentationController_tag","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"},"description":"Section slug — the tag name lowercased with dashes, as listed in `/documentation.md` or `/documentation/index.json`.","example":"ai-employees"}],"responses":{"200":{"description":"OpenAPI 3 document for the section","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"No documented endpoints for \"ai-employees\" — No section has that slug.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No documented endpoints for \"ai-employees\"","path":"/documentation/tag/{slug}.json","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"One section as an OpenAPI document","description":"One section's operations as a standalone OpenAPI document — what the reference page fetches when a section is opened.\n\n#### Signature\n\n```http\nGET /documentation/tag/{slug}.json (slug: string) -> OpenAPI 3 document for the section\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SECTION_NOT_FOUND | No documented endpoints for \"ai-employees\" | No section has that slug. | — |\n\nPlus the standard platform errors: `429`, `500`.","tags":["API reference"]}},"/robots.txt":{"get":{"operationId":"DocumentationController_robots","parameters":[],"responses":{"200":{"description":"robots.txt","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Crawler rules","description":"The reference is meant to be indexed; the API is not. Allows `/documentation`, `/documentation.md`, `/llms.txt` and `/openapi.json`, disallows everything else, and names `/documentation.md` as the sitemap.\n\n#### Signature\n\n```http\nGET /robots.txt () -> robots.txt\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.","tags":["API reference"]}},"/tools/tailwind-css/{siteName}/{pageName}":{"post":{"operationId":"ToolsController_tailwindCss","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"siteName","required":true,"in":"path","schema":{"type":"string"},"description":"Site name.","example":"acme-shop"},{"name":"pageName","required":true,"in":"path","schema":{"type":"string"},"description":"Page name.","example":"home"}],"responses":{"201":{"description":"The generated CSS","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate Tailwind CSS for a page","description":"Builds the Tailwind stylesheet a page actually needs from its markup — the compile step behind a published page.\n\n#### Signature\n\n```http\nPOST /tools/tailwind-css/{siteName}/{pageName} (siteName: string, pageName: string, body) -> The generated CSS\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/tailwind-map/{siteName}/{pageName}`","tags":["Tools"],"requestBody":{"description":"The page markup or build options.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"html":"<div class=\"p-4 text-lg\">…</div>"}}}}}},"/tools/tailwind-map/{siteName}/{pageName}":{"post":{"operationId":"ToolsController_tailwindMap","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"siteName","required":true,"in":"path","schema":{"type":"string"},"description":"Site name.","example":"acme-shop"},{"name":"pageName","required":true,"in":"path","schema":{"type":"string"},"description":"Page name.","example":"home"}],"responses":{"201":{"description":"The class map","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate a Tailwind class map for a page","description":"Returns the class-to-rule map for a page, for tooling that needs to know which utilities resolved to what.\n\n#### Signature\n\n```http\nPOST /tools/tailwind-map/{siteName}/{pageName} (siteName: string, pageName: string, body) -> The class map\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/tailwind-css/{siteName}/{pageName}`","tags":["Tools"],"requestBody":{"description":"The page markup or build options.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"html":"<div class=\"p-4\">…</div>"}}}}}},"/tools/default-templates":{"get":{"operationId":"ToolsController_listDefaultTemplates","parameters":[{"name":"deliveryType","required":false,"in":"query","schema":{"type":"string"},"example":"web"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Templates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List default templates","description":"The built-in site templates, optionally filtered by delivery type.\n\n#### Signature\n\n```http\nGET /tools/default-templates (deliveryType?: string) -> Templates\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/default-templates/{name}`","tags":["Tools"]}},"/tools/default-templates/{name}":{"get":{"operationId":"ToolsController_getDefaultTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Template name.","example":"storefront-basic"},{"name":"variant","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a default template","description":"One built-in template by name.\n\n#### Signature\n\n```http\nGET /tools/default-templates/{name} (name: string) -> The template\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/default-templates`","tags":["Tools"]}},"/tools/index-site/{siteName}":{"post":{"operationId":"ToolsController_indexSite","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"siteName","in":"path","required":true,"description":"Site name.","schema":{"type":"string"},"example":"acme-shop"}],"responses":{"201":{"description":"The indexing result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Index a site","description":"Rebuilds the search index for a site. Worth running after a bulk content change; on a large site it is not cheap.\n\n#### Signature\n\n```http\nPOST /tools/index-site/{siteName} (siteName: string) -> The indexing result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/create-page-image/{siteName}/{pageName}`","tags":["Tools"]}},"/tools/create-page-image/{siteName}/{pageName}":{"post":{"operationId":"ToolsController_generatePageImage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"siteName","in":"path","required":true,"description":"Site name.","schema":{"type":"string"},"example":"acme-shop"},{"name":"pageName","in":"path","required":true,"description":"Page name.","schema":{"type":"string"},"example":"home"}],"responses":{"201":{"description":"The generated image","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate a page image","description":"Renders a page to an image — the preview or social card for it. Rendering a page is slow, so treat this as a background job rather than a request-path call.\n\n#### Signature\n\n```http\nPOST /tools/create-page-image/{siteName}/{pageName} (siteName: string, pageName: string, body) -> The generated image\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/create-site-favicon/{siteName}`","tags":["Tools"],"requestBody":{"description":"Rendering options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"width":1200,"height":630}}}}}},"/tools/web-visit-direct":{"post":{"operationId":"ToolsController_webVisitDirect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`true` recorded, `false` ignored","content":{"application/json":{"schema":{"type":"boolean"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record a direct web visit","description":"Records a page view measured from this request: the caller's IP, user agent and forwarding headers are read here and merged with `data`. `data.url` must be the full page URL — a missing or relative URL, or one on the API's own host, is ignored. Answers `true` when recorded (or already recorded), `false` when ignored.\n\nVisits from bots, crawlers, scanners and uptime probes, requests for files or probe paths (assets, `.env`, `wp-*`, `_next`, `static/chunks`), local development hosts, internal IPs, and any IP past 60 visits a minute are dropped without error — the call answers `false`. An identical visit within 30 seconds is recorded once. Location is looked up from the IP before the row is written.\n\n#### Signature\n\n```http\nPOST /tools/web-visit-direct (body) -> `true` recorded, `false` ignored\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/web-visit-indirect`","tags":["Tools"],"requestBody":{"description":"The visit.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","additionalProperties":true,"required":["url"],"properties":{"url":{"type":"string"},"referrer":{"type":"string"},"pageTitle":{"type":"string"},"siteName":{"type":"string"},"deviceId":{"type":"string"},"sessionId":{"type":"string"},"type":{"type":"string","example":"pageview"}}}}},"example":{"data":{"url":"https://shop.example.com/pricing?utm_source=facebook","referrer":"https://www.facebook.com/","pageTitle":"Pricing","deviceId":"dev_7Kq2M9"}}}}}}},"/tools/web-visit-indirect":{"post":{"operationId":"ToolsController_webVisitIndirect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-info","in":"header","required":false,"description":"JSON client context from base-app's proxy (real IP, user agent, host). Fills gaps in `data`.","schema":{"type":"string"}}],"responses":{"201":{"description":"`true` recorded, `false` ignored","content":{"application/json":{"schema":{"type":"boolean"}}}},"404":{"description":"Site not found: acme-shop shop.example.com  in org acme — The site named by `configSite` / `host` does not belong to the org (not checked for `source: \"chat-client\"`).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Site not found: acme-shop shop.example.com  in org acme","path":"/tools/web-visit-indirect","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record a page view from the browser","description":"The page-view beacon — base-app's browser tracker sends one per view (pageview, unload, click and chat events alike, by `data.type`), and it is the only place a page view is recorded. A browser cannot see its own IP, so when base-app's proxy forwards `x-client-info` its IP, user agent and site fill whatever `data` left out; what the page sent wins. The site (`configSite` / `host`) must belong to the org, except for `source: \"chat-client\"` events. Answers `true` when recorded (or already recorded in the last 30 seconds), `false` when ignored.\n\nVisits from bots, crawlers, scanners and uptime probes, requests for files or probe paths (assets, `.env`, `wp-*`, `_next`, `static/chunks`), local development hosts, internal IPs, and any IP past 60 visits a minute are dropped without error — the call answers `false`. An identical visit within 30 seconds is recorded once. Location is looked up from the IP before the row is written.\n\n#### Signature\n\n```http\nPOST /tools/web-visit-indirect (body) -> `true` recorded, `false` ignored\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Self-reported data on a public route — figures can be inflated by anyone; the filters above only stop the obvious.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SITE_NOT_FOUND | Site not found: acme-shop shop.example.com  in org acme | The site named by `configSite` / `host` does not belong to the org (not checked for `source: \"chat-client\"`). | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/web-visit-direct`\n- `GET /analytics/live-view`","tags":["Tools"],"requestBody":{"description":"The visit.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","additionalProperties":true,"properties":{"url":{"type":"string"},"host":{"type":"string"},"configSite":{"type":"string"},"type":{"type":"string","example":"pageview"},"deviceId":{"type":"string"},"sessionId":{"type":"string"},"referrer":{"type":"string"},"source":{"type":"string"}}}}},"example":{"data":{"url":"https://shop.example.com/news/summer-gala","host":"shop.example.com","configSite":"acme-shop","type":"pageview","deviceId":"dev_7Kq2M9","sessionId":"ses_41"}}}}}}},"/tools/web-activity":{"post":{"operationId":"ToolsController_webActivity","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Nothing (empty body)"},"404":{"description":"Site not found: acme-shop in org acme — Neither `siteName` nor `domain` matches a site of the org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Site not found: acme-shop in org acme","path":"/tools/web-activity","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record web activity","description":"Stores the body as a record in the org, after checking that `siteName` or `domain` names one of the org's sites. The body is written as sent — it is a full record, including its `datatype`.\n\n#### Signature\n\n```http\nPOST /tools/web-activity (body) -> Nothing (empty body)\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Public, and the body decides what is written — treat as unsafe until it is restricted to activity records.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SITE_NOT_FOUND | Site not found: acme-shop in org acme | Neither `siteName` nor `domain` matches a site of the org. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/web-visit-indirect`","tags":["Tools"],"requestBody":{"description":"The record to store, with the site it belongs to.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","datatype":"web_visit","data":{"type":"click","url":"https://shop.example.com/pricing"}}}}}}},"/tools/create-site-favicon/{siteName}":{"post":{"operationId":"ToolsController_createSiteFavicon","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"siteName","in":"path","required":true,"description":"Site name.","schema":{"type":"string"},"example":"acme-shop"}],"responses":{"201":{"description":"The favicon","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate a site favicon","description":"Produces a favicon for a site from its branding.\n\n#### Signature\n\n```http\nPOST /tools/create-site-favicon/{siteName} (siteName: string, body) -> The favicon\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/create-page-image/{siteName}/{pageName}`","tags":["Tools"],"requestBody":{"description":"Options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/tools/fake-generate":{"post":{"operationId":"ToolsController_generateFakeData","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was generated","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate fake data","description":"Produces synthetic records for a data type — for populating a demo or testing a UI.\n\nIt writes real records into the org, so pointing it at production creates data that has to be cleaned up afterwards.\n\n#### Signature\n\n```http\nPOST /tools/fake-generate (body) -> What was generated\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Writes records into the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/default-templates`","tags":["Tools"],"requestBody":{"description":"What to generate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"datatype":"customer","count":25}}}}}},"/tools/domain/search/{domainName}/{tld}":{"get":{"operationId":"ToolsController_domainSearch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domainName","required":true,"in":"path","schema":{"type":"string"},"description":"Name to check, without the TLD.","example":"acme"},{"name":"tld","required":true,"in":"path","schema":{"type":"string"},"description":"TLD. Optional.","example":"com"},{"name":"suggest","required":false,"in":"query","schema":{"type":"boolean"},"description":"Include alternative suggestions.","example":true},{"name":"provider","required":false,"in":"query","schema":{"type":"string"},"example":"namecheap"}],"responses":{"200":{"description":"Availability and suggestions","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search for a domain","description":"Checks a domain's availability with the registrar, optionally returning suggestions. `tld` is an optional second segment; `provider` selects the registrar when more than one is configured.\n\n#### Signature\n\n```http\nGET /tools/domain/search/{domainName}/{tld} (domainName: string, tld: string, suggest?: boolean, provider?: string) -> Availability and suggestions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/search/advanced`","tags":["Tools"]}},"/tools/domain/search/advanced":{"post":{"operationId":"ToolsController_domainSearchAdvanced","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Availability and suggestions","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Search domains across TLDs","description":"Checks several keywords against several TLDs in one call, with suggestions capped by `maxSuggestions` (default 50). Each keyword × TLD is a registrar lookup, so a wide search is a slow one.\n\n#### Signature\n\n```http\nPOST /tools/domain/search/advanced (body) -> Availability and suggestions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/buy`","tags":["Tools"],"requestBody":{"description":"The search.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["keywords"],"properties":{"keywords":{"description":"One keyword or several.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"example":["acme","acmeshop"]},"tlds":{"type":"array","items":{"type":"string"},"example":["com","io"]},"includeSuggestions":{"type":"boolean","description":"Default true.","example":true},"maxSuggestions":{"type":"integer","description":"Default 50.","example":50},"provider":{"type":"string","example":"namecheap"}}},"example":{"keywords":["acme","acmeshop"],"tlds":["com","io"],"maxSuggestions":20}}}}}},"/tools/domain/dns/add":{"post":{"operationId":"ToolsController_addDNSRecord","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Add a DNS record","description":"Adds a record to a domain's zone. Live DNS: a wrong record can send traffic somewhere else or break mail delivery, and propagation means the mistake outlives the fix by the record's TTL.\n\n#### Signature\n\n```http\nPOST /tools/domain/dns/add (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Changes live DNS.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/dns/modify`","tags":["Tools"],"requestBody":{"description":"The record.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"orderId":{"type":"string","example":"ORD-4821"},"domain":{"type":"string","example":"example.com"},"type":{"type":"string","example":"A"},"name":{"type":"string","example":"www"},"values":{"type":"array","items":{"type":"string"},"example":["203.0.113.10"]},"provider":{"type":"string","example":"namecheap"}}},"example":{"domain":"example.com","type":"A","name":"www","values":["203.0.113.10"]}}}}}},"/tools/domain/dns/modify":{"post":{"operationId":"ToolsController_modifyDNSRecord","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Modify a DNS record","description":"Changes an existing DNS record. Same caution as adding one — this is live resolution, and MX or A record mistakes are outages.\n\n#### Signature\n\n```http\nPOST /tools/domain/dns/modify (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Changes live DNS.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/dns/delete`","tags":["Tools"],"requestBody":{"description":"The record change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"example.com","type":"A","name":"www","values":["203.0.113.11"]}}}}}},"/tools/domain/dns/delete":{"post":{"operationId":"ToolsController_deleteDNSRecord","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a DNS record","description":"Removes a DNS record. Deleting an A or MX record takes a site or its mail off the internet immediately — read the zone first.\n\n#### Signature\n\n```http\nPOST /tools/domain/dns/delete (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Removes live DNS resolution.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/dns/{domainName}/{type}`","tags":["Tools"],"requestBody":{"description":"Which record.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"example.com","type":"A","name":"www"}}}}}},"/tools/domain/dns/{domainName}/{type}":{"get":{"operationId":"ToolsController_getDNSRecords","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domainName","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"},{"name":"type","required":true,"in":"path","schema":{"type":"string"},"description":"Record type — `A`, `CNAME`, `MX`. Optional.","example":"A"},{"name":"provider","required":false,"in":"query","schema":{"type":"string"},"example":"namecheap"}],"responses":{"200":{"description":"DNS records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get DNS records","description":"The DNS records for a domain, optionally narrowed to one record type.\n\n#### Signature\n\n```http\nGET /tools/domain/dns/{domainName}/{type} (domainName: string, type: string, provider?: string) -> DNS records\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/dns/add`","tags":["Tools"]}},"/tools/domain/buy":{"post":{"operationId":"ToolsController_domainBuy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The registration order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Buy a domain","description":"Registers a domain with the registrar. **This spends money** and the registration is generally non-refundable — a typo in the domain name buys the typo.\n\nOn a public route, so treat access to this endpoint as the only control preventing arbitrary registrations against the account.\n\n#### Signature\n\n```http\nPOST /tools/domain/buy (body) -> The registration order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Spends money; non-refundable.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/list/{domainName}/{orderId}`","tags":["Tools"],"requestBody":{"description":"What to register.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"acme.com","years":1,"provider":"namecheap"}}}}}},"/tools/domain/list/{domainName}/{orderId}":{"get":{"operationId":"ToolsController_domainList","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domainName","required":true,"in":"path","schema":{"type":"string"},"description":"Optional domain filter.","example":"example.com"},{"name":"orderId","required":true,"in":"path","schema":{"type":"string"},"description":"Optional order filter.","example":"ORD-4821"},{"name":"provider","required":false,"in":"query","schema":{"type":"string"},"example":"namecheap"}],"responses":{"200":{"description":"Domains or the order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List domains or get an order","description":"Lists registered domains; both path segments are optional, narrowing to one domain or one registration order.\n\n#### Signature\n\n```http\nGET /tools/domain/list/{domainName}/{orderId} (domainName: string, orderId: string, provider?: string) -> Domains or the order\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/manage`","tags":["Tools"]}},"/tools/domain/manage":{"post":{"operationId":"ToolsController_domainManage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Manage a domain","description":"Registrar-side management — locks, auto-renew, contacts, nameservers. Changing nameservers moves where the domain resolves, which takes effect globally as caches expire.\n\n#### Signature\n\n```http\nPOST /tools/domain/manage (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Nameserver changes affect live resolution.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/dns/{domainName}/{type}`","tags":["Tools"],"requestBody":{"description":"The management action.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"example.com","action":"setNameservers","nameservers":["ns1.example.net","ns2.example.net"]}}}}}},"/tools/presentation/to-pdf":{"post":{"operationId":"ToolsController_pageToPdf","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The PDF result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Convert a page to PDF","description":"Renders a page to PDF. Rendering is slow — expect seconds, not milliseconds.\n\n#### Signature\n\n```http\nPOST /tools/presentation/to-pdf (body) -> The PDF result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/presentation/to-pptx`","tags":["Tools"],"requestBody":{"description":"What to convert.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","pageName":"deck"}}}}}},"/tools/presentation/to-pptx":{"post":{"operationId":"ToolsController_pageToPptx","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The PPTX result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Convert a page to PowerPoint","description":"Renders a page to a PPTX deck.\n\n#### Signature\n\n```http\nPOST /tools/presentation/to-pptx (body) -> The PPTX result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/presentation/to-pdf`","tags":["Tools"],"requestBody":{"description":"What to convert.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"siteName":"acme-shop","pageName":"deck"}}}}}},"/tools/presentation/publish":{"post":{"operationId":"ToolsController_hostHtmlPresentation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The hosted presentation and its URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Publish an HTML presentation","description":"Hosts an HTML presentation at a shareable URL. **The published page is publicly reachable** by anyone with the link — do not publish anything confidential.\n\n#### Signature\n\n```http\nPOST /tools/presentation/publish (body) -> The hosted presentation and its URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Publishes to a public URL.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/presentation/unpublish`","tags":["Tools"],"requestBody":{"description":"The presentation.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"q3-review","html":"<html>…</html>"}}}}}},"/tools/presentation/unpublish":{"post":{"operationId":"ToolsController_removeHostedPresentation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Unpublish a hosted presentation","description":"Removes a hosted presentation, so its URL stops resolving. Anyone who already downloaded it keeps their copy.\n\n#### Signature\n\n```http\nPOST /tools/presentation/unpublish (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/presentation/publish`","tags":["Tools"],"requestBody":{"description":"Which presentation.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"q3-review"}}}}}},"/tools/email-templates":{"get":{"operationId":"ToolsController_listEmailTemplates","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Templates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List default email templates","description":"The platform's built-in email templates — the registry an org customises from, not the org's own overrides.\n\n#### Signature\n\n```http\nGET /tools/email-templates () -> Templates\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/email-templates/{name}`","tags":["Tools"]}},"/tools/email-templates/{name}":{"get":{"operationId":"ToolsController_getEmailTemplate","parameters":[{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Template name.","example":"booking-confirmation"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a default email template","description":"One built-in email template by name, with its body.\n\n#### Signature\n\n```http\nGET /tools/email-templates/{name} (name: string) -> The template\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/email-templates`","tags":["Tools"]}},"/tools/remove-background":{"post":{"operationId":"ToolsController_removeBackground","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The processed image","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Remove an image background by URL","description":"Fetches an image by URL and returns it with the background removed.\n\nThe server fetches whatever URL it is given, which on a public route makes this usable to probe hosts the server can reach — treat the URL as untrusted input.\n\n#### Signature\n\n```http\nPOST /tools/remove-background (body) -> The processed image\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Server-side fetch of a caller-supplied URL.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/remove-background-file`","tags":["Tools"],"requestBody":{"description":"The image URL.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"url":"https://cdn.example.com/product.jpg"}}}}}},"/tools/remove-background-file":{"post":{"operationId":"ToolsController_removeBackgroundFromFile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The processed image","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Remove an image background from an upload","description":"Takes an uploaded image as `multipart/form-data` under the field name `file` and returns it with the background removed. The safer of the two forms, since nothing is fetched by the server.\n\n#### Signature\n\n```http\nPOST /tools/remove-background-file (body) -> The processed image\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/remove-background`","tags":["Tools"],"requestBody":{"description":"Multipart form with a `file` field.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/tools/domain/cloudflare/zone/create":{"post":{"operationId":"ToolsController_createCloudflareZone","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The zone, with its nameservers","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a Cloudflare zone","description":"Creates a zone in Cloudflare for a domain. The zone is not live until the domain's nameservers point at the ones Cloudflare issues.\n\n#### Signature\n\n```http\nPOST /tools/domain/cloudflare/zone/create (body) -> The zone, with its nameservers\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/cloudflare/zone/{domain}/nameservers`","tags":["Tools"],"requestBody":{"description":"The domain.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"example.com"}}}}}},"/tools/domain/cloudflare/zone/{domain}":{"get":{"operationId":"ToolsController_getCloudflareZone","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"}],"responses":{"200":{"description":"The zone","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a Cloudflare zone","description":"The zone record for a domain.\n\n#### Signature\n\n```http\nGET /tools/domain/cloudflare/zone/{domain} (domain: string) -> The zone\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/cloudflare/zone/{domain}/activation`","tags":["Tools"]}},"/tools/domain/cloudflare/zone/{domain}/activation":{"get":{"operationId":"ToolsController_checkCloudflareActivation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"}],"responses":{"200":{"description":"Activation status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Check Cloudflare zone activation","description":"Whether Cloudflare has seen the domain's nameservers change and activated the zone. Activation is what makes proxying and SSL start working, and it is not instant.\n\n#### Signature\n\n```http\nGET /tools/domain/cloudflare/zone/{domain}/activation (domain: string) -> Activation status\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/cloudflare/zone/{domain}/nameservers`","tags":["Tools"]}},"/tools/domain/cloudflare/zone/{domain}/nameservers":{"post":{"operationId":"ToolsController_updateCloudflareNameservers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update Cloudflare nameservers","description":"Points the domain's nameservers at Cloudflare. This is the switch that moves DNS authority; until caches expire, both the old and new answers circulate.\n\n#### Signature\n\n```http\nPOST /tools/domain/cloudflare/zone/{domain}/nameservers (domain: string, body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Moves DNS authority for the domain.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/cloudflare/zone/{domain}/activation`","tags":["Tools"],"requestBody":{"description":"The nameservers.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}},"get":{"operationId":"ToolsController_getCloudflareNameservers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"}],"responses":{"200":{"description":"The nameservers","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get Cloudflare nameservers","description":"The nameservers Cloudflare assigned to the zone — what has to be set at the registrar for the zone to activate.\n\n#### Signature\n\n```http\nGET /tools/domain/cloudflare/zone/{domain}/nameservers (domain: string) -> The nameservers\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/manage`","tags":["Tools"]}},"/tools/domain/cloudflare/transfer":{"post":{"operationId":"ToolsController_transferToCloudflare","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The transfer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Transfer a domain to Cloudflare","description":"Starts a registrar transfer into Cloudflare. Transfers are slow, need an auth code, and are refused inside 60 days of registration — check the status endpoint rather than expecting an immediate result.\n\n#### Signature\n\n```http\nPOST /tools/domain/cloudflare/transfer (body) -> The transfer\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Registrar transfer — slow and gated by registry rules.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/cloudflare/transfer/{domain}`","tags":["Tools"],"requestBody":{"description":"The transfer.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"example.com","authCode":"<auth code>"}}}}}},"/tools/domain/cloudflare/transfer/{domain}":{"get":{"operationId":"ToolsController_getCloudflareTransferStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"}],"responses":{"200":{"description":"Transfer status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get Cloudflare transfer status","description":"Where a registrar transfer has got to.\n\n#### Signature\n\n```http\nGET /tools/domain/cloudflare/transfer/{domain} (domain: string) -> Transfer status\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/cloudflare/transfer`","tags":["Tools"]}},"/tools/domain/cloudflare/ssl/{domain}":{"get":{"operationId":"ToolsController_getCloudflareSSL","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"}],"responses":{"200":{"description":"SSL settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get Cloudflare SSL settings","description":"The zone's SSL mode and certificate state.\n\n#### Signature\n\n```http\nGET /tools/domain/cloudflare/ssl/{domain} (domain: string) -> SSL settings\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/cloudflare/ssl/{domain}`","tags":["Tools"]},"post":{"operationId":"ToolsController_setCloudflareSSL","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain name.","example":"example.com"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update Cloudflare SSL settings","description":"Changes the zone's SSL mode. Getting this wrong breaks the site for every visitor at once — `full (strict)` against an origin with no valid certificate returns errors to everyone, and `flexible` in front of an HTTPS origin can loop.\n\n#### Signature\n\n```http\nPOST /tools/domain/cloudflare/ssl/{domain} (domain: string, body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- A wrong mode breaks the site immediately.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /tools/domain/cloudflare/ssl/{domain}`","tags":["Tools"],"requestBody":{"description":"The SSL setting.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"mode":"full"}}}}}},"/tools/domain/cloudflare/dns/import":{"post":{"operationId":"ToolsController_importCloudflareDNS","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The import result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Import DNS records into Cloudflare","description":"Bulk-imports records into a zone, typically from an existing provider's export. Import before switching nameservers — importing afterwards leaves a window where records are missing and the domain half-resolves.\n\n#### Signature\n\n```http\nPOST /tools/domain/cloudflare/dns/import (body) -> The import result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — the entire `tools` controller is a public route.\n- Import before the nameserver switch, not after.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /tools/domain/cloudflare/zone/{domain}/nameservers`","tags":["Tools"],"requestBody":{"description":"The records to import.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"domain":"example.com","records":[{"type":"A","name":"www","content":"203.0.113.10"}]}}}}}},"/usage/shared":{"get":{"operationId":"UsageController_getSharedUsage","summary":"Get shared services used by this org","description":"Every shared platform service: whether this org runs it on the shared account or its own (`account`), whether its agreement is accepted, and what it was charged in the window (default: this month). Our cost and margin are not included.\n\n#### Signature\n\n```http\nGET /usage/shared (from?: string, to?: string) -> Shared service usage\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/statement`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"from","required":false,"in":"query","description":"Start (ISO date). Default: start of this month.","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"End (ISO date). Default: now.","schema":{"type":"string"}}],"responses":{"200":{"description":"Shared service usage","content":{"application/json":{"schema":{"type":"object","properties":{"orgId":{"type":"string"},"period":{"type":"object","additionalProperties":true},"balance":{"type":"number"},"totals":{"type":"object","properties":{"charged":{"type":"number"},"count":{"type":"number"}}},"services":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"unitName":{"type":"string"},"pricingDisplay":{"type":"string"},"account":{"type":"string","nullable":true,"description":"shared or own, per provider."},"agreementAccepted":{"type":"boolean"},"count":{"type":"number"},"quantity":{"type":"number"},"charged":{"type":"number"},"lastUsedAt":{"type":"string","nullable":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/admin/service-usage":{"get":{"operationId":"UsageController_serviceUsage","summary":"Get service usage across the platform","description":"Who used the shared services in the window, from the charges each org recorded: platform totals (billed, our cost, margin), each service across all orgs, each org (searched, sorted, paged), the daily trend, uses with no cost recorded, refusals, and orgs using a service without its agreement.\n\n#### Signature\n\n```http\nGET /usage/admin/service-usage (from?: string, to?: string, q?: string, service?: string, sort?: string, dir?: string, page?: integer, pageSize?: integer) -> { period, totals, services, trend, orgs: { total, page, pageSize, data } }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | — | Service usage across organizations is only available to the platform (root or shared) organization. | The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one. | — |\n| `400` | — | from and to must be ISO dates | `from`/`to` do not parse, or `from` is after `to` (\"from must be before to\"). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"dir","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","required":false,"in":"query","schema":{"type":"string","enum":["name","uses","billed","cost","margin","uncosted","balance","refused","lastUsedAt"]}},{"name":"service","required":false,"in":"query","description":"Only orgs that used this service.","schema":{"type":"string"}},{"name":"q","required":false,"in":"query","description":"Org name or id contains.","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","description":"End (ISO date). Default: now.","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"Start (ISO date). Default: start of this month.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ period, totals, services, trend, orgs: { total, page, pageSize, data } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"from and to must be ISO dates — `from`/`to` do not parse, or `from` is after `to` (\"from must be before to\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"from and to must be ISO dates","path":"/usage/admin/service-usage","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Service usage across organizations is only available to the platform (root or shared) organization. — The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Service usage across organizations is only available to the platform (root or shared) organization.","path":"/usage/admin/service-usage","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Platform admin"]}},"/usage/admin/credits":{"get":{"operationId":"UsageController_credits","summary":"List every organization’s credit","description":"Each org’s balance (dollars), plan, status, what it was billed for shared services this month and when it last used one — searched, sorted and paged by the server — with platform totals.\n\n#### Signature\n\n```http\nGET /usage/admin/credits (q?: string, filter?: string, sort?: string, dir?: string, page?: integer, pageSize?: integer) -> { month, totals: { credit, owed, billedThisMonth, … }, total, page, pageSize, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | — | Service usage across organizations is only available to the platform (root or shared) organization. | The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"dir","required":false,"in":"query","schema":{"type":"string","enum":["asc","desc"]}},{"name":"sort","required":false,"in":"query","schema":{"type":"string","enum":["name","plan","balance","billedThisMonth","usesThisMonth","lastUsedAt","createdAt"]}},{"name":"filter","required":false,"in":"query","schema":{"type":"string","enum":["with-credit","no-credit","using"]}},{"name":"q","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ month, totals: { credit, owed, billedThisMonth, … }, total, page, pageSize, data }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Service usage across organizations is only available to the platform (root or shared) organization. — The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Service usage across organizations is only available to the platform (root or shared) organization.","path":"/usage/admin/credits","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Platform admin"]}},"/usage/admin/service-usage/orgs/{orgId}":{"get":{"operationId":"UsageController_orgServiceUsage","summary":"Get one organization’s service usage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgId","required":true,"in":"path","description":"Organization to read.","schema":{"type":"string"},"example":"org_4821"},{"name":"from","required":false,"in":"query","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ orgId, name, balance, period, totals: { uses, billed, cost, margin, uncosted, refused, servicesUsed }, services, … }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"from and to must be ISO dates — `from`/`to` do not parse, or `from` is after `to` (\"from must be before to\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"from and to must be ISO dates","path":"/usage/admin/service-usage/orgs/{orgId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Service usage across organizations is only available to the platform (root or shared) organization. — The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Service usage across organizations is only available to the platform (root or shared) organization.","path":"/usage/admin/service-usage/orgs/{orgId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No organization \"<orgId>\" — The org does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No organization \"<orgId>\"","path":"/usage/admin/service-usage/orgs/{orgId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Platform admin"],"description":"For one org: every catalog service with whether it runs on the shared account or its own, its agreement, uses, our cost, billed and margin, plus its trend and refusals.\n\n#### Signature\n\n```http\nGET /usage/admin/service-usage/orgs/{orgId} (orgId: string, from?: string, to?: string) -> { orgId, name, balance, period, totals: { uses, billed, cost, margin, uncosted, refused, servicesUsed }, services, … }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | — | Service usage across organizations is only available to the platform (root or shared) organization. | The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one. | — |\n| `400` | — | from and to must be ISO dates | `from`/`to` do not parse, or `from` is after `to` (\"from must be before to\"). | — |\n| `404` | — | No organization \"<orgId>\" | The org does not exist. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/admin/service-usage/orgs/{orgId}/charges`"}},"/usage/admin/service-usage/orgs/{orgId}/charges":{"get":{"operationId":"UsageController_orgServiceCharges","summary":"List one organization’s charges","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgId","required":true,"in":"path","description":"Organization to read.","schema":{"type":"string"},"example":"org_4821"},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"service","required":false,"in":"query","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ orgId, period, page, pageSize, total, data }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"from and to must be ISO dates — `from`/`to` do not parse, or `from` is after `to` (\"from must be before to\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"from and to must be ISO dates","path":"/usage/admin/service-usage/orgs/{orgId}/charges","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Service usage across organizations is only available to the platform (root or shared) organization. — The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Service usage across organizations is only available to the platform (root or shared) organization.","path":"/usage/admin/service-usage/orgs/{orgId}/charges","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Platform admin"],"description":"The org’s individual service charges in the window, newest first, paged.\n\n#### Signature\n\n```http\nGET /usage/admin/service-usage/orgs/{orgId}/charges (orgId: string, from?: string, to?: string, service?: string, page?: integer, pageSize?: integer) -> { orgId, period, page, pageSize, total, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootSystem`, `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | — | Service usage across organizations is only available to the platform (root or shared) organization. | The caller is signed in to any org other than the root or shared org — a tenant ConfigAdmin passes the role check but not this one. | — |\n| `400` | — | from and to must be ISO dates | `from`/`to` do not parse, or `from` is after `to` (\"from must be before to\"). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/usage/endpoints":{"get":{"operationId":"UsageController_getTokenizedEndpoints","summary":"List tokenized endpoints","description":"Every endpoint that consumes tokens, with its cost. The table behind what a call charges an org.\n\n#### Signature\n\n```http\nGET /usage/endpoints () -> Endpoints and their costs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /usage/endpoints`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Endpoints and their costs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]},"post":{"operationId":"UsageController_setEndpointCost","summary":"Set an endpoint cost","description":"Sets the token cost for an endpoint. Affects pricing for every organization, not just the caller. A cost set too high starts refusing calls for orgs with small balances; too low, and usage stops being paid for.\n\n#### Signature\n\n```http\nPOST /usage/endpoints (body) -> Confirmation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Affects pricing for every organization, not just the caller.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /usage/endpoints/{endpoint}`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The endpoint and its cost.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint","cost"],"properties":{"endpoint":{"type":"string","example":"POST /ai/chat"},"cost":{"type":"number","description":"Tokens per call.","example":10}}},"example":{"endpoint":"POST /ai/chat","cost":10}}}},"responses":{"201":{"description":"Confirmation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"success":true,"message":"Cost for POST /ai/chat set to 10 tokens"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/endpoints/{endpoint}":{"delete":{"operationId":"UsageController_removeEndpoint","summary":"Remove an endpoint from tokenization","description":"Stops charging for an endpoint — it becomes free for every organization. Affects pricing for every organization, not just the caller.\n\n#### Signature\n\n```http\nDELETE /usage/endpoints/{endpoint} (endpoint: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Affects pricing for every organization, not just the caller.\n- Makes the endpoint free platform-wide.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/endpoints`","parameters":[{"name":"endpoint","required":true,"in":"path","description":"The endpoint identifier.","schema":{"type":"string"},"example":"POST%20%2Fai%2Fchat"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/stats":{"get":{"operationId":"UsageController_getUsageStats","summary":"Get usage records","description":"The org's usage records (`usage`), optionally within a date range on `data.timestamp`, in the repository's paged list shape. `all=true` drops the org scope.\n\n#### Signature\n\n```http\nGET /usage/stats (startDate?: string, endDate?: string, all?: boolean) -> Paged usage records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `all=true` is not restricted to operators: any signed-in caller can pass it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/statement`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","description":"ISO date.","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"all","required":false,"in":"query","schema":{"type":"boolean"},"description":"Read without the org scope.","example":false}],"responses":{"200":{"description":"Paged usage records","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/statement":{"get":{"operationId":"UsageController_getStatement","summary":"Get the organization statement","description":"Every change to the balance in the window (default: the last 30 days), newest first and labelled with what each charge was for. Back-to-back charges for the same service are one row. `breakdown` totals spending by service. Amounts are full precision.\n\n#### Signature\n\n```http\nGET /usage/statement (from?: string, to?: string) -> The statement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | from/to must be ISO dates | `from` or `to` does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/balance`\n- `GET /usage/shared`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"from","required":false,"in":"query","description":"Start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","required":false,"in":"query","description":"End (ISO date). Default: now.","schema":{"type":"string"},"example":"2026-09-30"}],"responses":{"200":{"description":"The statement","content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"balance":{"type":"number","nullable":true},"totals":{"type":"object","properties":{"spent":{"type":"number"},"credited":{"type":"number"},"charges":{"type":"number"}}},"breakdown":{"type":"array","items":{"type":"object","additionalProperties":true}},"rows":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"from/to must be ISO dates — `from` or `to` does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"from/to must be ISO dates","path":"/usage/statement","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/balance":{"get":{"operationId":"UsageController_getBalance","summary":"Get the organization balance","description":"The org's credit balance (dollars), its spend rate (the multiplier applied to every charge), plan, whether it is active, and any promotion it qualifies for. Check it before a bulk AI job — a run that exhausts the balance part-way leaves the work half done.\n\n#### Signature\n\n```http\nGET /usage/balance () -> The balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Without the `orgid` header it answers `{ success: false, error: 'Missing orgid header' }` with status 200.\n- The response includes the full company record.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/statement`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The balance","content":{"application/json":{"schema":{"type":"object","properties":{"balance":{"type":"number"},"spendRate":{"type":"number"},"plan":{"type":"string"},"active":{"type":"boolean"},"orgId":{"type":"string"},"promotions":{"type":"array","items":{"type":"object","additionalProperties":true}},"company":{"type":"object","additionalProperties":true}}},"example":{"balance":42.18,"spendRate":1,"plan":"pro","active":true,"orgId":"org_4821","promotions":[]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/ai/provider-key":{"get":{"operationId":"UsageController_getAIProviderKey","summary":"Get an AI provider key and balance","description":"Returns the API key for an AI provider along with the org's balance, so a client can call the provider directly.\n\n**This hands out a provider credential.** Anything holding it can spend against that provider outside this platform's accounting — treat the response as a secret and prefer the server-side `/ai/*` endpoints where possible.\n\n#### Signature\n\n```http\nGET /usage/ai/provider-key (provider?: string) -> { success, orgId, provider, apiKey, balance, spendRate, plan, active }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns a live provider credential.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /usage/ai/charge`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"provider","required":true,"in":"query","description":"openai, anthropic, gemini, deepseek…","schema":{"type":"string"},"example":"openai"}],"responses":{"200":{"description":"{ success, orgId, provider, apiKey, balance, spendRate, plan, active }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/ai/models":{"get":{"operationId":"UsageController_getSupportedModels","summary":"List AI models and pricing","description":"Supported models and what each costs — read this to work out what a job will cost before running it.\n\n#### Signature\n\n```http\nGET /usage/ai/models () -> Models and pricing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /usage/ai/charge`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Models and pricing","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/ai/charge":{"post":{"operationId":"UsageController_chargeAiUsage","summary":"Charge AI usage","description":"Records AI consumption and **deducts it from the org's balance**. Token models are charged on `promptTokens` and `completionTokens`; image models on `imageCount` and `imageQuality` tiers. The org's spend rate is applied on top.\n\nCall it **after** a successful provider call, not before — charging for a call that failed bills the customer for nothing. It is also not idempotent: calling it twice for one completion charges twice.\n\n#### Signature\n\n```http\nPOST /usage/ai/charge (body) -> The charge result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Deducts real balance. Not idempotent.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/balance`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"What was consumed.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["provider","model"],"properties":{"provider":{"type":"string","example":"openai"},"model":{"type":"string","example":"gpt-5"},"promptTokens":{"type":"number","example":1200},"completionTokens":{"type":"number","example":800},"imageCount":{"type":"number","description":"Image models.","example":2},"imageQuality":{"type":"string","enum":["low","medium","high"],"description":"Image models.","example":"medium"},"metadata":{"type":"object","additionalProperties":true}}},"examples":{"tokens":{"summary":"A token model","value":{"provider":"openai","model":"gpt-5","promptTokens":1200,"completionTokens":800}},"images":{"summary":"An image model","value":{"provider":"openai","model":"gpt-image-1","imageCount":2,"imageQuality":"medium"}}}}}},"responses":{"201":{"description":"The charge result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/gift":{"post":{"operationId":"UsageController_giftCredit","summary":"Gift credit to another organization","description":"Moves `amount` of the calling org's balance to `toOrg`: the caller is debited, the recipient credited, and a `gift_deduction` transaction is recorded on the caller. Nothing is created from nothing — the caller must hold the amount.\n\n#### Signature\n\n```http\nPOST /usage/gift (body) -> { success, message }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A refused gift (amount ≤ 0, same org, unknown org, insufficient balance) is not an error: it answers `success: false`.\n- `notes` is recorded as who gifted it (`giftedBy`), and the transaction's own notes stay empty.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/balance`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The gift.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","toOrg"],"properties":{"amount":{"type":"number"},"toOrg":{"type":"string"},"notes":{"type":"string"}}},"example":{"toOrg":"org_4821","amount":20,"notes":"Trial extension"}}}},"responses":{"201":{"description":"{ success, message }","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"]}},"/usage/{orgId}/current/{type}":{"get":{"operationId":"UsageController_getCurrentUsage","parameters":[{"name":"orgId","required":true,"in":"path","schema":{"type":"string"},"description":"Org to read.","example":"org_4821"},{"name":"type","required":true,"in":"path","schema":{"type":"string"},"description":"Usage type. Optional.","example":"ai"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"{ orgId, type, usage: [{ year, month, count }] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"],"summary":"Get an organization's usage counts","description":"Usage counts for a named org over the last six months, optionally for one usage type — the same data as usage history, despite the name. The path org is explicit, so a caller with content read permission can read any org.\n\n#### Signature\n\n```http\nGET /usage/{orgId}/current/{type} (orgId: string, type: string) -> { orgId, type, usage: [{ year, month, count }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/history/{type}`"}},"/usage/history/{type}":{"get":{"operationId":"UsageController_getUsageHistory","parameters":[{"name":"orgId","required":true,"in":"path","schema":{"type":"string"}},{"name":"type","required":true,"in":"path","schema":{"type":"string"},"description":"Usage type. Optional.","example":"ai"},{"name":"months","required":false,"in":"query","schema":{"type":"integer","default":6},"example":6},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"{ orgId, type, history: [{ year, month, count }] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage"],"summary":"Get usage history","description":"Usage counts for each of the last `months` months, optionally for one usage type. Requires content read permission.\n\n#### Signature\n\n```http\nGET /usage/history/{type} (type: string, months?: integer) -> { orgId, type, history: [{ year, month, count }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The route reads the org from a path parameter it does not declare, so `orgId` is empty and the counts are not scoped as intended; every month also reports the same all-time count.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/statement`"}},"/service-pricing/initialize":{"post":{"operationId":"ServicePricingController_initializeDefaults","summary":"Initialize default service pricing","parameters":[],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"Seeds the default pricing catalogue. Intended for a fresh platform — run against a populated catalogue it may overwrite deliberate pricing decisions, so check what exists first.\n\n#### Signature\n\n```http\nPOST /service-pricing/initialize () -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Notes\n\n- Can overwrite existing pricing.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /service-pricing/list`"}},"/service-pricing/list":{"get":{"operationId":"ServicePricingController_list","summary":"List service pricing","parameters":[{"name":"provider","required":false,"in":"query","schema":{"type":"string"},"example":"openai"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"}],"responses":{"200":{"description":"Service pricing","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"Every priced service, optionally filtered by provider.\n\n#### Signature\n\n```http\nGET /service-pricing/list (provider?: string, status?: string) -> Service pricing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /service-pricing/active`"}},"/service-pricing":{"post":{"operationId":"ServicePricingController_create","summary":"Create service pricing","parameters":[],"responses":{"201":{"description":"The pricing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"Adds a priced service to the catalogue. Affects pricing for every organization, not just the caller.\n\n#### Signature\n\n```http\nPOST /service-pricing (body) -> The pricing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Notes\n\n- Affects pricing for every organization, not just the caller.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /service-pricing/{name}`","requestBody":{"description":"The pricing.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"ai-chat","provider":"openai","cost":10}}}}}},"/service-pricing/{name}":{"put":{"operationId":"ServicePricingController_update","summary":"Update service pricing","parameters":[{"name":"name","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"ai-chat"}],"responses":{"200":{"description":"The updated pricing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"Changes a service's price. Takes effect on the next call — it does not re-rate usage already charged, and every organization sees the new price at once.\n\n#### Signature\n\n```http\nPUT /service-pricing/{name} (name: string, body) -> The updated pricing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Notes\n\n- Affects pricing for every organization, not just the caller.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /service-pricing/list`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"cost":12}}}}},"delete":{"operationId":"ServicePricingController_delete","summary":"Delete service pricing","parameters":[{"name":"name","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"ai-chat"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"Removes a service's pricing. Anything that charges for it loses its price — check what depends on the entry before deleting it.\n\n#### Signature\n\n```http\nDELETE /service-pricing/{name} (name: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Notes\n\n- Affects pricing for every organization, not just the caller.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /service-pricing/all`"}},"/service-pricing/all":{"delete":{"operationId":"ServicePricingController_deleteAll","summary":"Delete all service pricing","parameters":[],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"**Empties the entire pricing catalogue** for every organization. Nothing is priced afterwards, so charging stops working platform-wide until the catalogue is rebuilt.\n\nThere is no confirmation and no undo. Almost certainly not what you want — delete a single entry instead.\n\n#### Signature\n\n```http\nDELETE /service-pricing/all () -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootPowerUser`, `ConfigAdmin`.\n\n#### Notes\n\n- Destroys the whole catalogue platform-wide. No undo.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /service-pricing/{name}`\n- `POST /service-pricing/initialize`"}},"/service-pricing/active":{"get":{"operationId":"ServicePricingController_getActiveServices","summary":"Get active services","parameters":[],"responses":{"200":{"description":"Active services","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"The services currently priced and available.\n\n#### Signature\n\n```http\nGET /service-pricing/active () -> Active services\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /service-pricing/catalog`"}},"/service-pricing/catalog":{"get":{"operationId":"ServicePricingController_getServicesCatalog","summary":"Get the services catalog","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"The catalog","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"The catalogue as it applies to an organization — what it can use and at what price.\n\n#### Signature\n\n```http\nGET /service-pricing/catalog () -> The catalog\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /service-pricing/check-balance/{service}`"}},"/service-pricing/check-balance/{service}":{"get":{"operationId":"ServicePricingController_checkBalance","summary":"Check the balance for a service","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"service","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"ai-chat"},{"name":"amount","required":true,"in":"query","schema":{"type":"number"},"description":"Amount needed, in dollars. Unparseable values count as 0.","example":2.5}],"responses":{"200":{"description":"{ sufficient, balance }","content":{"application/json":{"schema":{"type":"object","properties":{"sufficient":{"type":"boolean"},"balance":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"Whether the org's balance covers `amount` right now — the pre-flight check before starting work that would fail part-way for lack of balance. When it does not, the org's admins are sent an insufficient-credit alert (throttled).\n\n#### Signature\n\n```http\nGET /service-pricing/check-balance/{service} (service: string, amount?: number) -> { sufficient, balance }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /usage/balance`"}},"/service-pricing/{service}":{"get":{"operationId":"ServicePricingController_get","summary":"Get service pricing","parameters":[{"name":"service","required":true,"in":"path","description":"Service name.","schema":{"type":"string"},"example":"ai-chat"}],"responses":{"200":{"description":"The pricing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Usage · Service pricing"],"description":"The price of one service.\n\n#### Signature\n\n```http\nGET /service-pricing/{service} (service: string) -> The pricing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /service-pricing/{name}`"}},"/ai-employees/me/hello":{"post":{"operationId":"AIEmployeeController_hello","summary":"Say hello from its runtime","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"{ ok, you, status, next }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"you":{"type":"string","description":"Its title."},"status":{"type":"string"},"next":{"type":"string","example":"GET /ai-employees/me/briefing"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This is for AI employees, signed in as themselves. — The caller is not an AI employee's own user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This is for AI employees, signed in as themselves.","path":"/ai-employees/me/hello","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"The AI employee, once running and signed in as itself, says what runs it. This is the proof from its side that it exists and can reach the platform; it marks the employee connected and is shown on its setup checklist.\n\n#### Signature\n\n```http\nPOST /ai-employees/me/hello (body) -> { ok, you, status, next }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `AI (the employee itself)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | AI_EMPLOYEE_ONLY | This is for AI employees, signed in as themselves. | The caller is not an AI employee's own user. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `GET /ai-employees/me/briefing`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string"},"agentId":{"type":"string"},"model":{"type":"string"},"version":{"type":"string"}}},"example":{"provider":"session-manager","agentId":"ag_4821","model":"claude-sonnet-5","version":"1.4.0"}}}}}},"/ai-employees/me/briefing":{"get":{"operationId":"AIEmployeeController_briefing","summary":"Get its briefing","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"The briefing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"identity":{"type":"object","additionalProperties":true},"functions":{"type":"object","properties":{"api":{"type":"string"},"reference":{"type":"string"},"everything":{"type":"string"}}},"company":{"type":"object","additionalProperties":true},"knowledge":{"type":"object","additionalProperties":true},"status":{"type":"string"},"work":{"type":"object","properties":{"counts":{"type":"object","additionalProperties":true},"open":{"type":"array","items":{"type":"object","additionalProperties":true}},"waitingOnAPerson":{"type":"array","items":{"type":"object","additionalProperties":true}},"recentDecisions":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"now":{"type":"object","properties":{"iso":{"type":"string"},"local":{"type":"string"},"timezone":{"type":"string"}}},"notes":{"type":"array","items":{"type":"object","additionalProperties":true}},"team":{"type":"object","additionalProperties":true},"meetings":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This is for AI employees, signed in as themselves. — The caller is not an AI employee's own user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This is for AI employees, signed in as themselves.","path":"/ai-employees/me/briefing","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No AI employee uses this sign-in. — The signed-in AI user belongs to no employee (it was removed).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee uses this sign-in.","path":"/ai-employees/me/briefing","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Everything the employee needs on every sign-in and job, current: who it is (identity, supervisor, what it listens to, its approval policy, its phone numbers and voice), the company and its knowledge, its instructions (platform, org, its own job description), where to find the API reference, where its work stands (open jobs, what waits on a person, recent decisions), the time where it works, its own notes, the AI team workspace and its meetings in the next day.\n\n#### Signature\n\n```http\nGET /ai-employees/me/briefing () -> The briefing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `AI (the employee itself)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | AI_EMPLOYEE_ONLY | This is for AI employees, signed in as themselves. | The caller is not an AI employee's own user. | — |\n| `404` | NO_EMPLOYEE_FOR_SIGN_IN | No AI employee uses this sign-in. | The signed-in AI user belongs to no employee (it was removed). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /ai-employees/worker/{name}/lease`"}},"/ai-employees/worker/{name}/summary":{"get":{"operationId":"AIEmployeeController_summary","summary":"What an employee did over a period","description":"For its reports: job counts by outcome, approvals asked / approved / rejected / waiting, cost, check-ins (pings), and each job it finished. Pings on which it did nothing are not counted as work. An AI employee may call this only for itself.\n\n#### Signature\n\n```http\nGET /ai-employees/worker/{name}/summary (name: string, since?: string) -> { since, until, counts, approvals, costUsd, pings, items }\n```\n\n#### Access\n\nRequires either a bearer JWT or an API key. Required role(s): `System`, `AI (for its own jobs)`, `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `403` | NOT_OWN_JOB | An AI employee can only work on its own jobs. | An AI employee called this for another employee or another employee's job. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"},{"name":"since","required":false,"in":"query","description":"ISO date-time; default 24 hours ago.","schema":{"type":"string"},"example":"2026-09-28T00:00:00Z"}],"responses":{"200":{"description":"{ since, until, counts, approvals, costUsd, pings, items }","content":{"application/json":{"schema":{"type":"object","properties":{"since":{"type":"string"},"until":{"type":"string"},"counts":{"type":"object","properties":{"done":{"type":"integer"},"failed":{"type":"integer"},"cancelled":{"type":"integer"},"waitingApproval":{"type":"integer"},"queued":{"type":"integer"},"inProgress":{"type":"integer"}}},"approvals":{"type":"object","properties":{"asked":{"type":"integer"},"approved":{"type":"integer"},"rejected":{"type":"integer"},"waiting":{"type":"integer"}}},"costUsd":{"type":"number"},"pings":{"type":"integer"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"source":{"type":"string"},"status":{"type":"string"},"result":{"type":"string"},"finishedAt":{"type":"string"},"costUsd":{"type":"number"},"ref":{"type":"object","additionalProperties":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"An AI employee can only work on its own jobs. — An AI employee called this for another employee or another employee's job.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"An AI employee can only work on its own jobs.","path":"/ai-employees/worker/{name}/summary","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/worker/{name}/summary","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"]}},"/ai-employees/worker/{name}/lease":{"post":{"operationId":"AIEmployeeController_lease","summary":"Lease the next job","description":"The runtime asks for the employee's next job. The platform decides when it works: when it is not active, off shift, over today's budget, at its concurrency limit, or has nothing queued, the answer is `{ job: null, reason }` (still a success). A job this worker already held and was interrupted on is handed back first; otherwise in-progress work whose lease lapsed, then queued work by priority (high, normal, low), oldest first. A queued job that has used its attempts is failed and the next one is tried.\n\nA lease lasts 15 minutes and is extended by each step reported. The answer carries the job, the employee's profile and approval policy, the company context and the budget left today.\n\n#### Signature\n\n```http\nPOST /ai-employees/worker/{name}/lease (name: string, body) -> The leased job, or `{ job: null, reason }`\n```\n\n#### Access\n\nRequires either a bearer JWT or an API key. Required role(s): `System`, `AI (for its own jobs)`, `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `403` | NOT_OWN_JOB | An AI employee can only work on its own jobs. | An AI employee called this for another employee or another employee's job. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /ai-employees/worker/work/{id}/step`\n- `POST /ai-employees/worker/work/{id}/complete`","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"The leased job, or `{ job: null, reason }`","content":{"application/json":{"schema":{"type":"object","properties":{"job":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}},"nullable":true},"reason":{"type":"string","description":"Only when `job` is null: why nothing was leased."},"leaseUntil":{"type":"string","format":"date-time"},"organization":{"type":"object","nullable":true,"properties":{"company":{"type":"object","additionalProperties":true},"knowledge":{"type":"object","additionalProperties":true},"escalation":{"type":"object","additionalProperties":true}}},"employee":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"jobTitle":{"type":"string"},"jobDescription":{"type":"string"},"supervisor":{"type":"string","nullable":true},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"runtime":{"type":"object","additionalProperties":true},"remainingBudgetUsd":{"type":"number","nullable":true}}}}},"examples":{"idle":{"summary":"Nothing to do","value":{"job":null,"reason":"Outside its working hours."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"An AI employee can only work on its own jobs. — An AI employee called this for another employee or another employee's job.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"An AI employee can only work on its own jobs.","path":"/ai-employees/worker/{name}/lease","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/worker/{name}/lease","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"worker":{"type":"string","description":"Which runtime process holds the lease. Defaults to the caller's identity."}}},"example":{"worker":"runner-1"}}}}}},"/ai-employees/worker/work/{id}/step":{"post":{"operationId":"AIEmployeeController_step","summary":"Report a step on a job","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"201":{"description":"The job with the new step","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"400":{"description":"A step needs text. — `text` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A step needs text.","path":"/ai-employees/worker/work/{id}/step","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"An AI employee can only work on its own jobs. — An AI employee called this for another employee or another employee's job.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"An AI employee can only work on its own jobs.","path":"/ai-employees/worker/work/{id}/step","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/worker/work/{id}/step","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This job is queued, not in progress. — The job is not in progress (queued, waiting for approval, or finished).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This job is queued, not in progress.","path":"/ai-employees/worker/work/{id}/step","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Adds a step to the job's timeline and extends the lease by 15 minutes. `costUsd` is added to the job's cost (and counts against today's budget). An unknown `kind` is recorded as `note`.\n\n#### Signature\n\n```http\nPOST /ai-employees/worker/work/{id}/step (id: string, body) -> The job with the new step\n```\n\n#### Access\n\nRequires either a bearer JWT or an API key. Required role(s): `System`, `AI (for its own jobs)`, `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n| `403` | NOT_OWN_JOB | An AI employee can only work on its own jobs. | An AI employee called this for another employee or another employee's job. | — |\n| `409` | JOB_NOT_IN_PROGRESS | This job is queued, not in progress. | The job is not in progress (queued, waiting for approval, or finished). | Lease it first. |\n| `400` | STEP_TEXT_REQUIRED | A step needs text. | `text` is missing. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"worker":{"type":"string","description":"Which runtime process holds the lease. Defaults to the caller's identity."},"kind":{"type":"string","enum":["note","thinking","action","check","screen","error"],"default":"note"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"costUsd":{"type":"number","minimum":0}}},"example":{"kind":"action","text":"Replied to the customer with the delivery date.","costUsd":0.012}}}}}},"/ai-employees/worker/work/{id}/approval":{"post":{"operationId":"AIEmployeeController_requestApproval","summary":"Ask for approval before an action","description":"The runtime asks before an action its policy may reserve for a person. Answered at once with `{ allowed: true }` when the employee's approval policy allows the action, or when the same request was already approved (on this job, or on another job in the last day — for money, up to the amount approved). Otherwise the job waits: status `waiting_approval`, lease released, `{ allowed: false, waiting: true }`. It is leased back with the decision in its steps once a person decides. `other` (or any unknown action) always needs a person.\n\n#### Signature\n\n```http\nPOST /ai-employees/worker/work/{id}/approval (id: string, body) -> `{ allowed: true, approvedBy? }` or `{ allowed: false, waiting: true }`\n```\n\n#### Access\n\nRequires either a bearer JWT or an API key. Required role(s): `System`, `AI (for its own jobs)`, `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n| `403` | NOT_OWN_JOB | An AI employee can only work on its own jobs. | An AI employee called this for another employee or another employee's job. | — |\n| `409` | JOB_NOT_IN_PROGRESS | This job is queued, not in progress. | The job is not in progress (queued, waiting for approval, or finished). | Lease it first. |\n| `400` | SUMMARY_REQUIRED | Say what it wants to do (summary). | `summary` is missing. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /ai-employees/work/{id}/decide`","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"201":{"description":"`{ allowed: true, approvedBy? }` or `{ allowed: false, waiting: true }`","content":{"application/json":{"schema":{"type":"object","properties":{"allowed":{"type":"boolean"},"approvedBy":{"type":"string"},"waiting":{"type":"boolean"}}}}}},"400":{"description":"Say what it wants to do (summary). — `summary` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say what it wants to do (summary).","path":"/ai-employees/worker/work/{id}/approval","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"An AI employee can only work on its own jobs. — An AI employee called this for another employee or another employee's job.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"An AI employee can only work on its own jobs.","path":"/ai-employees/worker/work/{id}/approval","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/worker/work/{id}/approval","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This job is queued, not in progress. — The job is not in progress (queued, waiting for approval, or finished).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This job is queued, not in progress.","path":"/ai-employees/worker/work/{id}/approval","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["summary"],"properties":{"worker":{"type":"string","description":"Which runtime process holds the lease. Defaults to the caller's identity."},"action":{"type":"string","enum":["delete","bulkSend","calls","money","usersAndPermissions","other"],"default":"other"},"summary":{"type":"string","description":"What it wants to do, in words a person can decide on."},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"}}},"example":{"action":"money","summary":"Refund order #1043 ($42.50) — the item arrived broken.","amountUsd":42.5}}}}}},"/ai-employees/worker/work/{id}/complete":{"post":{"operationId":"AIEmployeeController_complete","summary":"Finish a job","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"201":{"description":"The finished job","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"An AI employee can only work on its own jobs. — An AI employee called this for another employee or another employee's job.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"An AI employee can only work on its own jobs.","path":"/ai-employees/worker/work/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/worker/work/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This job is queued, not in progress. — The job is not in progress (queued, waiting for approval, or finished).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This job is queued, not in progress.","path":"/ai-employees/worker/work/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Marks the job done with its result and releases the lease. `costUsd` is added to the job's cost. A ping on which it took an action is retitled \"On its own: <first line of the result>\" and counted as work.\n\n#### Signature\n\n```http\nPOST /ai-employees/worker/work/{id}/complete (id: string, body) -> The finished job\n```\n\n#### Access\n\nRequires either a bearer JWT or an API key. Required role(s): `System`, `AI (for its own jobs)`, `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n| `403` | NOT_OWN_JOB | An AI employee can only work on its own jobs. | An AI employee called this for another employee or another employee's job. | — |\n| `409` | JOB_NOT_IN_PROGRESS | This job is queued, not in progress. | The job is not in progress (queued, waiting for approval, or finished). | Lease it first. |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"worker":{"type":"string","description":"Which runtime process holds the lease. Defaults to the caller's identity."},"result":{"type":"string"},"costUsd":{"type":"number","minimum":0}}},"example":{"result":"Confirmed the reservation for 7pm and emailed the guest.","costUsd":0.03}}}}}},"/ai-employees/worker/work/{id}/fail":{"post":{"operationId":"AIEmployeeController_fail","summary":"Report a failed attempt","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"201":{"description":"The job — queued again, or failed","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"An AI employee can only work on its own jobs. — An AI employee called this for another employee or another employee's job.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"An AI employee can only work on its own jobs.","path":"/ai-employees/worker/work/{id}/fail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/worker/work/{id}/fail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This job is queued, not in progress. — The job is not in progress (queued, waiting for approval, or finished).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This job is queued, not in progress.","path":"/ai-employees/worker/work/{id}/fail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"A failed attempt goes back in the queue until the job has used the employee's `limits.maxAttempts`; then (or with `retry: false`) the job is failed with the error.\n\n#### Signature\n\n```http\nPOST /ai-employees/worker/work/{id}/fail (id: string, body) -> The job — queued again, or failed\n```\n\n#### Access\n\nRequires either a bearer JWT or an API key. Required role(s): `System`, `AI (for its own jobs)`, `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n| `403` | NOT_OWN_JOB | An AI employee can only work on its own jobs. | An AI employee called this for another employee or another employee's job. | — |\n| `409` | JOB_NOT_IN_PROGRESS | This job is queued, not in progress. | The job is not in progress (queued, waiting for approval, or finished). | Lease it first. |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"worker":{"type":"string","description":"Which runtime process holds the lease. Defaults to the caller's identity."},"error":{"type":"string"},"retry":{"type":"boolean","default":true},"costUsd":{"type":"number","minimum":0}}},"example":{"error":"The customer record is locked by another user."}}}}}},"/ai-employees/config":{"get":{"operationId":"AIEmployeeController_getConfig","summary":"Get the org's AI employee settings","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ config, from, isPlatform }","content":{"application/json":{"schema":{"type":"object","properties":{"config":{"type":"object","additionalProperties":true},"from":{"type":"object","additionalProperties":{"type":"string","enum":["org","platform","none"]}},"isPlatform":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"The settings in force, section by section (`company`, `knowledge`, `defaults`, `escalation`): the org's own, else the platform's (the shared org's), else empty — and where each came from.\n\n#### Signature\n\n```http\nGET /ai-employees/config () -> { config, from, isPlatform }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"AIEmployeeController_saveConfig","summary":"Save the org's AI employee settings","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"The settings in force after saving (as GET)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"\"owner@\" is not an email address. — An escalation contact that is not an email, whenStuck not ask/fail/pause, a knowledge source without a valid type or reference (a url that is not http(s)), a knowledge entry without title or content, an unknown timezone, `system` from an org other than the platform, or minutes out of range. Every problem is listed, joined; the body also carries `problems[]`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"\"owner@\" is not an email address.","path":"/ai-employees/config","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Saves the sections sent; a section sent as `null` goes back to inheriting; a section left out is untouched. `system` (ping, unclaimed and handling minutes) is the platform's only — the shared org may set it, and a change applies to every switched-on employee at once.\n\n#### Signature\n\n```http\nPUT /ai-employees/config (body) -> The settings in force after saving (as GET)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | AI_EMPLOYEE_INVALID | \"owner@\" is not an email address. | An escalation contact that is not an email, whenStuck not ask/fail/pause, a knowledge source without a valid type or reference (a url that is not http(s)), a knowledge entry without title or content, an unknown timezone, `system` from an org other than the platform, or minutes out of range. Every problem is listed, joined; the body also carries `problems[]`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"company":{"type":"object","additionalProperties":true},"knowledge":{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"sourceType":{"type":"string","enum":["collection","document","url","custom"]},"reference":{"type":"string"}}}},"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"content":{"type":"string"}}}}}},"defaults":{"type":"object","description":"Defaults for new hires: runtime, workingHours, limits, approvals."},"escalation":{"type":"object","properties":{"contact":{"type":"string","format":"email"},"whenStuck":{"type":"string","enum":["ask","fail","pause"]}}},"system":{"type":"object","properties":{"pingMinutes":{"type":"integer","minimum":1},"unclaimedMinutes":{"type":"integer","minimum":1},"handlingMinutes":{"type":"integer","minimum":5}}}}},"example":{"escalation":{"contact":"owner@example.com","whenStuck":"ask"},"knowledge":null}}}}}},"/ai-employees/templates":{"get":{"operationId":"AIEmployeeController_templates","summary":"List templates to hire from","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data, isPlatform }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"sk":{"type":"string"},"source":{"type":"string","enum":["platform","org"],"description":"platform: the shared org's, read-only here. org: this org's own."},"name":{"type":"string"},"title":{"type":"string"},"category":{"type":"string","enum":["front-desk","sales","support","finance","marketing","operations","other"]},"icon":{"type":"string"},"summary":{"type":"string"},"jobTitle":{"type":"string"},"jobDescription":{"type":"string"},"suggestedGroups":{"type":"array","items":{"type":"string"}},"listensTo":{"type":"array","items":{"type":"string"}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"avatar":{"type":"string"}}}},"isPlatform":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Every template this org can hire from in one list: the platform's (the shared org's — read-only here: use it or copy it) and the org's own. Sorted by category and title.\n\n#### Signature\n\n```http\nGET /ai-employees/templates () -> { data, isPlatform }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai-employees`"},"put":{"operationId":"AIEmployeeController_saveTemplate","summary":"Save one of the org's templates","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"The saved template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Give the template a name. Give the template a title. — No name/title. Every problem is listed, joined; the body also carries `problems[]`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give the template a name. Give the template a title.","path":"/ai-employees/templates","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"An AI employee is already called \"night-desk\". Give the template another name. — An employee has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"An AI employee is already called \"night-desk\". Give the template another name.","path":"/ai-employees/templates","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Creates or changes one of the org's own templates (in the shared org: one of the platform's). Only template fields are kept. An org saving a new template under a platform template's name gets a `-copy` name instead of shadowing it.\n\n#### Signature\n\n```http\nPUT /ai-employees/templates (body) -> The saved template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | AI_EMPLOYEE_INVALID | Give the template a name. Give the template a title. | No name/title. Every problem is listed, joined; the body also carries `problems[]`. | — |\n| `409` | NAME_TAKEN_BY_EMPLOYEE | An AI employee is already called \"night-desk\". Give the template another name. | An employee has that handle. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"source":{"type":"string","enum":["platform","org"],"description":"platform: the shared org's, read-only here. org: this org's own."},"name":{"type":"string"},"title":{"type":"string"},"category":{"type":"string","enum":["front-desk","sales","support","finance","marketing","operations","other"]},"icon":{"type":"string"},"summary":{"type":"string"},"jobTitle":{"type":"string"},"jobDescription":{"type":"string"},"suggestedGroups":{"type":"array","items":{"type":"string"}},"listensTo":{"type":"array","items":{"type":"string"}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"avatar":{"type":"string"}}},"example":{"title":"Night desk","category":"front-desk","jobTitle":"Night receptionist","listensTo":["chat_queue","sms"]}}}}}},"/ai-employees/templates/{name}":{"get":{"operationId":"AIEmployeeController_template","summary":"Get one template","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Template name.","example":"front-desk"}],"responses":{"200":{"description":"The template","content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"source":{"type":"string","enum":["platform","org"],"description":"platform: the shared org's, read-only here. org: this org's own."},"name":{"type":"string"},"title":{"type":"string"},"category":{"type":"string","enum":["front-desk","sales","support","finance","marketing","operations","other"]},"icon":{"type":"string"},"summary":{"type":"string"},"jobTitle":{"type":"string"},"jobDescription":{"type":"string"},"suggestedGroups":{"type":"array","items":{"type":"string"}},"listensTo":{"type":"array","items":{"type":"string"}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"avatar":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No template \"front-desk\" — Neither the org nor the platform has a template by that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No template \"front-desk\"","path":"/ai-employees/templates/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"One template by name; the org's own wins over the platform's of the same name.\n\n#### Signature\n\n```http\nGET /ai-employees/templates/{name} (name: string) -> The template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TEMPLATE_NOT_FOUND | No template \"front-desk\" | Neither the org nor the platform has a template by that name. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"delete":{"operationId":"AIEmployeeController_deleteTemplate","summary":"Delete one of the org's templates","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Template name.","example":"night-desk"}],"responses":{"200":{"description":"{ deleted }","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"This organization has no template \"night-desk\" of its own to delete. — No template of the org's own by that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"This organization has no template \"night-desk\" of its own to delete.","path":"/ai-employees/templates/{name}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Deletes one of the org's own templates. The platform's are not the org's to delete.\n\n#### Signature\n\n```http\nDELETE /ai-employees/templates/{name} (name: string) -> { deleted }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TEMPLATE_NOT_FOUND | This organization has no template \"night-desk\" of its own to delete. | No template of the org's own by that name. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/instructions":{"get":{"operationId":"AIEmployeeController_instructions","summary":"Get the org's instructions to its AI employees","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data, platform, isPlatform }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"content":{"type":"string"},"enabled":{"type":"boolean","default":true},"who":{"type":"string","enum":["all","only","except"],"default":"all"},"employees":{"type":"array","items":{"type":"string"},"description":"Handles, for only / except."},"order":{"type":"integer","readOnly":true}}}},"platform":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"content":{"type":"string"},"enabled":{"type":"boolean","default":true},"who":{"type":"string","enum":["all","only","except"],"default":"all"},"employees":{"type":"array","items":{"type":"string"},"description":"Handles, for only / except."},"order":{"type":"integer","readOnly":true}}}},"isPlatform":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"The instructions the org gives its AI employees, in order. The platform's own system instructions also apply to every employee but are shown only in the shared org (`platform` is empty elsewhere).\n\n#### Signature\n\n```http\nGET /ai-employees/instructions () -> { data, platform, isPlatform }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /ai-employees/{name}/instructions`"},"put":{"operationId":"AIEmployeeController_saveInstructions","summary":"Replace the org's instructions","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"The instructions after saving (as GET)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Send the instructions as a list. — `instructions` is not an array.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Send the instructions as a list.","path":"/ai-employees/instructions","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Replaces the whole list, in the order given. Each employee the change affects is told which instruction is new, changed or no longer applies to it (in its next briefing). In the shared org these are the platform's system instructions.\n\n#### Signature\n\n```http\nPUT /ai-employees/instructions (body) -> The instructions after saving (as GET)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INSTRUCTIONS_NOT_A_LIST | Send the instructions as a list. | `instructions` is not an array. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["instructions"],"properties":{"instructions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"content":{"type":"string"},"enabled":{"type":"boolean","default":true},"who":{"type":"string","enum":["all","only","except"],"default":"all"},"employees":{"type":"array","items":{"type":"string"},"description":"Handles, for only / except."},"order":{"type":"integer","readOnly":true}}}}}},"example":{"instructions":[{"title":"Refunds","content":"Never promise a refund; ask for approval with the order number.","who":"all"},{"title":"Spanish","content":"Answer in Spanish when the customer writes in Spanish.","who":"only","employees":["ava"]}]}}}}}},"/ai-employees/team":{"get":{"operationId":"AIEmployeeController_getTeam","summary":"Get the AI team workspace","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ workspace }","content":{"application/json":{"schema":{"type":"object","properties":{"workspace":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"title":{"type":"string"},"isPrivate":{"type":"boolean"},"memberCount":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"The org's AI team workspace — where its AI employees get goals, tasks and meetings, report, and ask for approvals. Made by the first hire.\n\n#### Signature\n\n```http\nGET /ai-employees/team () -> { workspace }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"AIEmployeeController_setTeam","summary":"Set the AI team workspace","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ workspace } (as GET)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Choose a workspace, not a conversation. — The id is missing or is a conversation.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Choose a workspace, not a conversation.","path":"/ai-employees/team","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Uses the workspace given (one the caller can see), or makes a new private one when none is given. Every AI employee and the caller are added to it.\n\n#### Signature\n\n```http\nPUT /ai-employees/team (body) -> { workspace } (as GET)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_A_WORKSPACE | Choose a workspace, not a conversation. | The id is missing or is a conversation. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspaceId":{"type":"string"}}},"example":{"workspaceId":"6710c0ffee0000000000beef"}}}}}},"/ai-employees/work":{"get":{"operationId":"AIEmployeeController_listWork","summary":"List work items","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"source","required":false,"in":"query","schema":{"type":"string","enum":["assigned","ping","direct","message","call","config"]}},{"name":"pings","required":false,"in":"query","schema":{"type":"boolean","default":false}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":25},"description":"Up to 200."},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"q","required":false,"in":"query","schema":{"type":"string"},"description":"Text in the title."},{"name":"status","required":false,"in":"query","description":"Comma-separated statuses.","schema":{"type":"string"},"example":"queued,in_progress"},{"name":"employee","required":false,"in":"query","schema":{"type":"string"},"description":"Handle."}],"responses":{"200":{"description":"{ page, pageSize, total, data }","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"data":{"type":"array","items":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Work across employees, most recently changed first. Pings on which the employee did nothing are left out unless `pings=true`.\n\n#### Signature\n\n```http\nGET /ai-employees/work (employee?: string, status?: string, q?: string, source?: string, pings?: boolean, page?: integer, pageSize?: integer) -> { page, pageSize, total, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/work/approvals":{"get":{"operationId":"AIEmployeeController_approvals","summary":"List what waits for an approval","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ total, data }","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"data":{"type":"array","items":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Every work item waiting for a person to approve, oldest first (up to 200).\n\n#### Signature\n\n```http\nGET /ai-employees/work/approvals () -> { total, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai-employees/work/{id}/decide`"}},"/ai-employees/work/{id}":{"get":{"operationId":"AIEmployeeController_getWork","summary":"Get a work item","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"200":{"description":"The work item","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/work/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"One work item with its full timeline. Attached records are described from the records themselves.\n\n#### Signature\n\n```http\nGET /ai-employees/work/{id} (id: string) -> The work item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/work/{id}/decide":{"post":{"operationId":"AIEmployeeController_decide","summary":"Approve or reject a request","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"201":{"description":"The work item","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"400":{"description":"decision must be \"approved\" or \"rejected\". — `decision` is anything else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"decision must be \"approved\" or \"rejected\".","path":"/ai-employees/work/{id}/decide","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"An AI employee cannot decide approvals. — The caller is an AI employee.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"An AI employee cannot decide approvals.","path":"/ai-employees/work/{id}/decide","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/work/{id}/decide","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This work is not waiting for an approval. — The job is not `waiting_approval`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This work is not waiting for an approval.","path":"/ai-employees/work/{id}/decide","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"A person's answer to what the employee asked permission for. The job goes back to its runtime either way (in progress, unleased) with the decision in its steps. The same request waiting on the employee's other jobs (same action, summary and amount) gets the same answer.\n\n#### Signature\n\n```http\nPOST /ai-employees/work/{id}/decide (id: string, body) -> The work item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DECISION_INVALID | decision must be \"approved\" or \"rejected\". | `decision` is anything else. | — |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n| `403` | AI_CANNOT_DECIDE | An AI employee cannot decide approvals. | The caller is an AI employee. | — |\n| `409` | NOT_WAITING | This work is not waiting for an approval. | The job is not `waiting_approval`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"decision":{"type":"string","enum":["approved","rejected"]},"note":{"type":"string"}}},"example":{"decision":"approved","note":"Fine — refund to the original card."}}}}}},"/ai-employees/work/{id}/cancel":{"post":{"operationId":"AIEmployeeController_cancel","summary":"Cancel a work item","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"201":{"description":"The cancelled work item","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/work/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"It is already done. — The job is done, failed or cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"It is already done.","path":"/ai-employees/work/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"#### Signature\n\n```http\nPOST /ai-employees/work/{id}/cancel (id: string) -> The cancelled work item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n| `409` | ALREADY_FINISHED | It is already done. | The job is done, failed or cancelled. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/work/{id}/retry":{"post":{"operationId":"AIEmployeeController_retry","summary":"Put failed or cancelled work back in the queue","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Work item id (`ai_employee_work` sk).","example":"6710c0ffee0000000000abcd"}],"responses":{"201":{"description":"The queued work item","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such work item — No `ai_employee_work` has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such work item","path":"/ai-employees/work/{id}/retry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Only failed or cancelled work can be retried. — The job is queued, in progress, waiting or done.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Only failed or cancelled work can be retried.","path":"/ai-employees/work/{id}/retry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Queues the job again with its attempts reset and its error cleared.\n\n#### Signature\n\n```http\nPOST /ai-employees/work/{id}/retry (id: string) -> The queued work item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORK_NOT_FOUND | No such work item | No `ai_employee_work` has that id. | — |\n| `409` | NOT_RETRYABLE | Only failed or cancelled work can be retried. | The job is queued, in progress, waiting or done. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees":{"get":{"operationId":"AIEmployeeController_list","summary":"List AI employees","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ totals, data }","content":{"application/json":{"schema":{"type":"object","properties":{"totals":{"type":"object","properties":{"employees":{"type":"integer"},"active":{"type":"integer"},"queued":{"type":"integer"},"inProgress":{"type":"integer"},"waitingApproval":{"type":"integer"},"spentTodayUsd":{"type":"number"}}},"data":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"jobTitle":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","active","paused"]},"runtime":{"type":"string","enum":["stub","session-manager","external"]},"supervisor":{"type":"string","nullable":true},"connection":{"type":"object","additionalProperties":true,"description":"When its runtime was first and last heard from, and whether it is connected now."},"queue":{"type":"object","additionalProperties":{"type":"integer"},"description":"Work counts by status."},"spentTodayUsd":{"type":"number"},"budgetEnabled":{"type":"boolean"},"dailyBudgetUsd":{"type":"number","nullable":true,"description":"Today's budget including credit; null when the budget is off."},"budgetToday":{"type":"object","properties":{"creditUsd":{"type":"number"},"resetAt":{"type":"string","nullable":true}}},"onShift":{"type":"boolean"},"idleReason":{"type":"string","nullable":true,"description":"Why it takes no work now, e.g. \"Outside its working hours.\" — null when it can work."},"activity":{"type":"object","nullable":true,"properties":{"text":{"type":"string"},"at":{"type":"string"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Every AI employee (templates excluded) with its queue, spend today, whether it is on shift, why it is idle, and totals across all of them.\n\n#### Signature\n\n```http\nGET /ai-employees () -> { totals, data }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"post":{"operationId":"AIEmployeeController_create","summary":"Hire an AI employee","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"The created employee","content":{"application/json":{"schema":{"type":"object","description":"An AI employee (`ai_employee`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","readOnly":true}}}}}}}},"400":{"description":"Give it a name people will call it (title). — No title; handle characters; a supervisor that is not an email; unknown listensTo channels; voice platform/voice/greeting/eagerness/speakingSpeed/tools invalid; unknown timezone. Every problem is listed, joined; the body also carries `problems[]`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give it a name people will call it (title).","path":"/ai-employees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No template \"front-desk\" — The named template does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No template \"front-desk\"","path":"/ai-employees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"There is already an AI employee called \"ava\". Choose another handle. — An employee or template has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"There is already an AI employee called \"ava\". Choose another handle.","path":"/ai-employees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Creates the employee and the user it works as (`<name>@<orgId>.ai-employee.local`, no password, locked until switched on, in the `AI` group only). It starts from the org's defaults, then the `template` if one is named, then what is sent. It starts as `draft`: give it groups (`PUT /ai-employees/{name}/access`) and switch it on. Its supervisor gets a direct conversation with it, and it joins (or creates) the AI team workspace with the person who hired it.\n\n#### Signature\n\n```http\nPOST /ai-employees (body) -> The created employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | AI_EMPLOYEE_INVALID | Give it a name people will call it (title). | No title; handle characters; a supervisor that is not an email; unknown listensTo channels; voice platform/voice/greeting/eagerness/speakingSpeed/tools invalid; unknown timezone. Every problem is listed, joined; the body also carries `problems[]`. | — |\n| `404` | TEMPLATE_NOT_FOUND | No template \"front-desk\" | The named template does not exist. | — |\n| `409` | HANDLE_TAKEN | There is already an AI employee called \"ava\". Choose another handle. | An employee or template has that handle. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /ai-employees/templates`\n- `PUT /ai-employees/{name}/access`\n- `POST /ai-employees/{name}/switch-on`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","description":"Template name to start from."}}},"example":{"title":"Ava","template":"front-desk","supervisor":"owner@example.com","listensTo":["chat_queue","sms"],"workingHours":{"alwaysOn":false,"timezone":"America/Chicago","days":["mon","tue","wed","thu","fri"],"start":"08:00","end":"18:00"}}}}}}},"/ai-employees/{name}/instructions":{"get":{"operationId":"AIEmployeeController_instructionsFor","summary":"Everything an employee is told","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"200":{"description":"{ data: [{ title, content, from }] }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"content":{"type":"string"},"from":{"type":"string","enum":["platform","org","employee"]}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/instructions","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Its instructions in the order it gets them: the org's that apply to it (`from: org`), then its own job description (`from: employee`). The platform's system instructions come first too, but are listed only in the shared org.\n\n#### Signature\n\n```http\nGET /ai-employees/{name}/instructions (name: string) -> { data: [{ title, content, from }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/{name}/provision":{"post":{"operationId":"AIEmployeeController_provision","summary":"Create the agent on its provider","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"The provider status after provisioning","content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"}}},"status":{"type":"object","properties":{"exists":{"type":"boolean","nullable":true},"state":{"type":"string"},"detail":{"type":"string"},"checkedAt":{"type":"string"}}},"connection":{"type":"object","additionalProperties":true,"description":"What the agent itself last reported (hello / briefing / lease / step)."}}}}}},"400":{"description":"There is no connection to the provider \"external\" yet. — The employee's `runtime.provider` has no connection here.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"There is no connection to the provider \"external\" yet.","path":"/ai-employees/{name}/provision","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/provision","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"The provider refused. — The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"The provider refused.","path":"/ai-employees/{name}/provision","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["AI Employees"],"description":"Creates the agent on its runtime provider with its brief (and, for a provider that signs in as it, a fresh sign-in). `stub` records the provisioning and does no work.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/provision (name: string) -> The provider status after provisioning\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | NO_PROVIDER | There is no connection to the provider \"external\" yet. | The employee's `runtime.provider` has no connection here. | — |\n| `502` | PROVIDER_ERROR | The provider refused. | The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /ai-employees/{name}/provider-status`"}},"/ai-employees/{name}/provider-status":{"get":{"operationId":"AIEmployeeController_providerStatus","summary":"Ask its provider whether the agent is running","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"200":{"description":"{ provider, status, connection }","content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"}}},"status":{"type":"object","properties":{"exists":{"type":"boolean","nullable":true},"state":{"type":"string"},"detail":{"type":"string"},"checkedAt":{"type":"string"}}},"connection":{"type":"object","additionalProperties":true,"description":"What the agent itself last reported (hello / briefing / lease / step)."}}}}}},"400":{"description":"There is no connection to the provider \"external\" yet. — The employee's `runtime.provider` has no connection here.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"There is no connection to the provider \"external\" yet.","path":"/ai-employees/{name}/provider-status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/provider-status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"What the provider says (exists, state), beside when the agent itself was last heard from. A provider that does not answer gives `state: error` with the reason, not an HTTP error.\n\n#### Signature\n\n```http\nGET /ai-employees/{name}/provider-status (name: string) -> { provider, status, connection }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | NO_PROVIDER | There is no connection to the provider \"external\" yet. | The employee's `runtime.provider` has no connection here. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/{name}/handover/sign-in":{"post":{"operationId":"AIEmployeeController_issueSignIn","summary":"Issue a sign-in for the system that runs it","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"The sign-in, shown once","content":{"application/json":{"schema":{"type":"object","properties":{"apiBaseUrl":{"type":"string"},"orgId":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"authenticatorSecret":{"type":"string"},"otpauthUrl":{"type":"string"},"shownOnce":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/handover/sign-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Its user record is missing — recreate the employee. — The employee's user was deleted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Its user record is missing — recreate the employee.","path":"/ai-employees/{name}/handover/sign-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"A new password and a new authenticator secret for its user, replacing any earlier ones (earlier sign-ins stop working). Returned **once** and never stored in the clear — lose it and issue another. Give it only to the system that runs this employee; that system signs in as the employee and gets its own token.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/handover/sign-in (name: string) -> The sign-in, shown once\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Notes\n\n- Treat the response as a credential.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `409` | USER_MISSING | Its user record is missing — recreate the employee. | The employee's user was deleted. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/{name}":{"get":{"operationId":"AIEmployeeController_detail","summary":"Get an AI employee","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","description":"The employee's handle (`ai_employee.name`).","schema":{"type":"string"},"example":"ava"}],"responses":{"200":{"description":"The employee","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"jobTitle":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","active","paused"]},"runtime":{"type":"string","enum":["stub","session-manager","external"]},"supervisor":{"type":"string","nullable":true},"connection":{"type":"object","additionalProperties":true,"description":"When its runtime was first and last heard from, and whether it is connected now."},"queue":{"type":"object","additionalProperties":{"type":"integer"},"description":"Work counts by status."},"spentTodayUsd":{"type":"number"},"budgetEnabled":{"type":"boolean"},"dailyBudgetUsd":{"type":"number","nullable":true,"description":"Today's budget including credit; null when the budget is off."},"budgetToday":{"type":"object","properties":{"creditUsd":{"type":"number"},"resetAt":{"type":"string","nullable":true}}},"onShift":{"type":"boolean"},"idleReason":{"type":"string","nullable":true,"description":"Why it takes no work now, e.g. \"Outside its working hours.\" — null when it can work."},"activity":{"type":"object","nullable":true,"properties":{"text":{"type":"string"},"at":{"type":"string"}}},"setup":{"type":"array","description":"The setup checklist: created, given access, (sign-in issued, for external), provisioned, switched on, connected, first job finished.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"done":{"type":"boolean"}}}},"record":{"type":"object","description":"An AI employee (`ai_employee`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","readOnly":true}}}}},"access":{"type":"object","nullable":true,"properties":{"userId":{"type":"string"},"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"locked":{"type":"boolean"}}},"contact":{"type":"object","properties":{"email":{"type":"string"},"phones":{"type":"array","items":{"type":"object","additionalProperties":true}},"numbers":{"type":"object","additionalProperties":true}}},"team":{"type":"object","nullable":true,"properties":{"workspaceId":{"type":"string"},"title":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"One employee: its state (as in the list), its setup checklist, its record, its user's access, its contact details and phone numbers, and the AI team workspace.\n\n#### Signature\n\n```http\nGET /ai-employees/{name} (name: string) -> The employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"AIEmployeeController_update","summary":"Change an AI employee","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"200":{"description":"The updated employee","content":{"application/json":{"schema":{"type":"object","description":"An AI employee (`ai_employee`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","readOnly":true}}}}}}}},"400":{"description":"\"Mars/Olympus\" is not a timezone. — As on create. Every problem is listed, joined; the body also carries `problems[]`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"\"Mars/Olympus\" is not a timezone.","path":"/ai-employees/{name}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Changes its profile, hours, limits, approvals, voice and job description. `name`, `userId`, `userEmail`, `status`, `kind` and `voiceAgent` are the server's and ignored here — status changes through switch-on / pause / shut-down. `limits` and `voice` merge into what it has. The employee is told what changed in its next briefing; a new title also renames its user.\n\n#### Signature\n\n```http\nPUT /ai-employees/{name} (name: string, body) -> The updated employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | AI_EMPLOYEE_INVALID | \"Mars/Olympus\" is not a timezone. | As on create. Every problem is listed, joined; the body also carries `problems[]`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","readOnly":true}}},"example":{"limits":{"dailyBudgetUsd":10},"approvals":{"money":"allow","moneyThresholdUsd":50}}}}}},"delete":{"operationId":"AIEmployeeController_remove","summary":"Remove an AI employee","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"200":{"description":"{ removed, cancelledWork, provider }","content":{"application/json":{"schema":{"type":"object","properties":{"removed":{"type":"string"},"cancelledWork":{"type":"integer"},"provider":{"nullable":true,"description":"What the provider said on removal; null when it was never provisioned."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Takes the agent off its provider, cancels its open work, locks and deactivates its user, removes its voice agent and takes it out of the AI team workspace. Work history stays.\n\n#### Signature\n\n```http\nDELETE /ai-employees/{name} (name: string) -> { removed, cancelledWork, provider }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/{name}/phones":{"post":{"operationId":"AIEmployeeController_setPhone","summary":"Assign a phone number, or release it","description":"Gives one of the org's numbers to the employee (calls to it are then answered by it, and its outbound calls come from it), or takes it away with `assign: false`. Other people on the number keep it. The employee is told.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/phones (name: string, body) -> { phoneNumber, assigned, phones }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `409` | NO_USER | Ava has no user yet, so no number can be assigned to it. | The employee has no login identity. | — |\n| `400` | NUMBER_REQUIRED | Say which number (phoneId or phoneNumber). | Neither given. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"{ phoneNumber, assigned, phones }","content":{"application/json":{"schema":{"type":"object","properties":{"phoneNumber":{"type":"string"},"assigned":{"type":"boolean"},"phones":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Say which number (phoneId or phoneNumber). — Neither given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say which number (phoneId or phoneNumber).","path":"/ai-employees/{name}/phones","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/phones","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Ava has no user yet, so no number can be assigned to it. — The employee has no login identity.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Ava has no user yet, so no number can be assigned to it.","path":"/ai-employees/{name}/phones","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phoneId":{"type":"string"},"phoneNumber":{"type":"string","example":"+15125550143"},"assign":{"type":"boolean","default":true},"release":{"type":"object","description":"Free the number from the AI line it is on first.","properties":{"source":{"type":"string"},"ref":{"type":"string"}}}}},"example":{"phoneNumber":"+15125550143","assign":true}}}}}},"/ai-employees/{name}/access":{"get":{"operationId":"AIEmployeeController_access","summary":"Get its access","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"200":{"description":"{ email, groups, always, available }","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}},"always":{"type":"array","items":{"type":"string"},"example":["AI"]},"available":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/access","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"The groups its user holds (besides `AI`, which it always has), and the org's groups it could be given (platform/Root groups never).\n\n#### Signature\n\n```http\nGET /ai-employees/{name}/access (name: string) -> { email, groups, always, available }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"AIEmployeeController_setAccess","summary":"Set its access","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"200":{"description":"Its access after the change (as GET)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"groups must be a list of group names. — `groups` is not an array.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"groups must be a list of group names.","path":"/ai-employees/{name}/access","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/access","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Its user record is missing — recreate the employee. — The employee's user was deleted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Its user record is missing — recreate the employee.","path":"/ai-employees/{name}/access","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Gives its user exactly these groups (plus `AI`), through the same user service User Management uses. The employee is told its new groups.\n\n#### Signature\n\n```http\nPUT /ai-employees/{name}/access (name: string, body) -> Its access after the change (as GET)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GROUPS_NOT_A_LIST | groups must be a list of group names. | `groups` is not an array. | — |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `409` | USER_MISSING | Its user record is missing — recreate the employee. | The employee's user was deleted. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["groups"],"properties":{"groups":{"type":"array","items":{"type":"string"}}}},"example":{"groups":["FrontDesk","Reservations"]}}}}}},"/ai-employees/{name}/switch-on":{"post":{"operationId":"AIEmployeeController_switchOn","summary":"Switch it on","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"The employee (as GET)","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"jobTitle":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","active","paused"]},"runtime":{"type":"string","enum":["stub","session-manager","external"]},"supervisor":{"type":"string","nullable":true},"connection":{"type":"object","additionalProperties":true,"description":"When its runtime was first and last heard from, and whether it is connected now."},"queue":{"type":"object","additionalProperties":{"type":"integer"},"description":"Work counts by status."},"spentTodayUsd":{"type":"number"},"budgetEnabled":{"type":"boolean"},"dailyBudgetUsd":{"type":"number","nullable":true,"description":"Today's budget including credit; null when the budget is off."},"budgetToday":{"type":"object","properties":{"creditUsd":{"type":"number"},"resetAt":{"type":"string","nullable":true}}},"onShift":{"type":"boolean"},"idleReason":{"type":"string","nullable":true,"description":"Why it takes no work now, e.g. \"Outside its working hours.\" — null when it can work."},"activity":{"type":"object","nullable":true,"properties":{"text":{"type":"string"},"at":{"type":"string"}}},"setup":{"type":"array","description":"The setup checklist: created, given access, (sign-in issued, for external), provisioned, switched on, connected, first job finished.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"done":{"type":"boolean"}}}},"record":{"type":"object","description":"An AI employee (`ai_employee`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","readOnly":true}}}}},"access":{"type":"object","nullable":true,"properties":{"userId":{"type":"string"},"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"locked":{"type":"boolean"}}},"contact":{"type":"object","properties":{"email":{"type":"string"},"phones":{"type":"array","items":{"type":"object","additionalProperties":true}},"numbers":{"type":"object","additionalProperties":true}}},"team":{"type":"object","nullable":true,"properties":{"workspaceId":{"type":"string"},"title":{"type":"string"}}}}}}}},"400":{"description":"Give Ava access first: choose at least one group for it under Access. Without one it can do nothing. — Its user has no group or role besides `AI`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give Ava access first: choose at least one group for it under Access. Without one it can do nothing.","path":"/ai-employees/{name}/switch-on","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/switch-on","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Its user record is missing — recreate the employee. — The employee's user was deleted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Its user record is missing — recreate the employee.","path":"/ai-employees/{name}/switch-on","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Status `active`: it takes work, its user is unlocked, it is pinged on schedule, and a shut-down agent is started again on its provider. It needs at least one group first.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/switch-on (name: string) -> The employee (as GET)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `409` | USER_MISSING | Its user record is missing — recreate the employee. | The employee's user was deleted. | — |\n| `400` | NO_ACCESS | Give Ava access first: choose at least one group for it under Access. Without one it can do nothing. | Its user has no group or role besides `AI`. | Set its access, then switch it on. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /ai-employees/{name}/access`"}},"/ai-employees/{name}/budget":{"post":{"operationId":"AIEmployeeController_budget","summary":"Adjust today's budget","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"The employee (as GET)","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"jobTitle":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","active","paused"]},"runtime":{"type":"string","enum":["stub","session-manager","external"]},"supervisor":{"type":"string","nullable":true},"connection":{"type":"object","additionalProperties":true,"description":"When its runtime was first and last heard from, and whether it is connected now."},"queue":{"type":"object","additionalProperties":{"type":"integer"},"description":"Work counts by status."},"spentTodayUsd":{"type":"number"},"budgetEnabled":{"type":"boolean"},"dailyBudgetUsd":{"type":"number","nullable":true,"description":"Today's budget including credit; null when the budget is off."},"budgetToday":{"type":"object","properties":{"creditUsd":{"type":"number"},"resetAt":{"type":"string","nullable":true}}},"onShift":{"type":"boolean"},"idleReason":{"type":"string","nullable":true,"description":"Why it takes no work now, e.g. \"Outside its working hours.\" — null when it can work."},"activity":{"type":"object","nullable":true,"properties":{"text":{"type":"string"},"at":{"type":"string"}}},"setup":{"type":"array","description":"The setup checklist: created, given access, (sign-in issued, for external), provisioned, switched on, connected, first job finished.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"done":{"type":"boolean"}}}},"record":{"type":"object","description":"An AI employee (`ai_employee`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","readOnly":true}}}}},"access":{"type":"object","nullable":true,"properties":{"userId":{"type":"string"},"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"locked":{"type":"boolean"}}},"contact":{"type":"object","properties":{"email":{"type":"string"},"phones":{"type":"array","items":{"type":"object","additionalProperties":true}},"numbers":{"type":"object","additionalProperties":true}}},"team":{"type":"object","nullable":true,"properties":{"workspaceId":{"type":"string"},"title":{"type":"string"}}}}}}}},"400":{"description":"action must be \"reset\" or \"credit\". — Any other action.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"action must be \"reset\" or \"credit\".","path":"/ai-employees/{name}/budget","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/budget","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"`reset` counts spend again from now; `credit` adds `amountUsd` on top of today's budget. Both lapse at midnight in its timezone. Budget alerts can fire again after either.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/budget (name: string, body) -> The employee (as GET)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | BUDGET_ACTION_INVALID | action must be \"reset\" or \"credit\". | Any other action. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["reset","credit"]},"amountUsd":{"type":"number","description":"For credit; more than 0."}}},"examples":{"reset":{"summary":"Start today's count again","value":{"action":"reset"}},"credit":{"summary":"Add $5 for today","value":{"action":"credit","amountUsd":5}}}}}}}},"/ai-employees/{name}/pause":{"post":{"operationId":"AIEmployeeController_pause","summary":"Pause it","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"The employee (as GET)","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"title":{"type":"string"},"jobTitle":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","active","paused"]},"runtime":{"type":"string","enum":["stub","session-manager","external"]},"supervisor":{"type":"string","nullable":true},"connection":{"type":"object","additionalProperties":true,"description":"When its runtime was first and last heard from, and whether it is connected now."},"queue":{"type":"object","additionalProperties":{"type":"integer"},"description":"Work counts by status."},"spentTodayUsd":{"type":"number"},"budgetEnabled":{"type":"boolean"},"dailyBudgetUsd":{"type":"number","nullable":true,"description":"Today's budget including credit; null when the budget is off."},"budgetToday":{"type":"object","properties":{"creditUsd":{"type":"number"},"resetAt":{"type":"string","nullable":true}}},"onShift":{"type":"boolean"},"idleReason":{"type":"string","nullable":true,"description":"Why it takes no work now, e.g. \"Outside its working hours.\" — null when it can work."},"activity":{"type":"object","nullable":true,"properties":{"text":{"type":"string"},"at":{"type":"string"}}},"setup":{"type":"array","description":"The setup checklist: created, given access, (sign-in issued, for external), provisioned, switched on, connected, first job finished.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"done":{"type":"boolean"}}}},"record":{"type":"object","description":"An AI employee (`ai_employee`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","description":"Handle; made from `title` when not given (lowercase, letters/numbers/-/_). Unique among employees and templates.","example":"ava"},"title":{"type":"string","description":"What people call it.","example":"Ava"},"jobTitle":{"type":"string","example":"Front desk"},"jobDescription":{"type":"string","description":"Its standing instructions (rich text)."},"supervisor":{"type":"string","description":"Email of the person it reports to — approvals and reports go there."},"listensTo":{"type":"array","items":{"type":"string","enum":["chat_queue","email","sms","social","ticket","order","form"]},"description":"Where it is alerted from. Always on: direct messages, mentions, work assigned to it."},"avatar":{"type":"string"},"runtime":{"type":"object","properties":{"provider":{"type":"string","enum":["stub","session-manager","external"],"default":"stub","description":"stub records what it would do and does no work."},"model":{"type":"string"}}},"workingHours":{"type":"object","properties":{"alwaysOn":{"type":"boolean","default":true},"timezone":{"type":"string","default":"UTC","example":"America/Chicago"},"days":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}},"limits":{"type":"object","properties":{"budgetEnabled":{"type":"boolean","default":true,"description":"Off: never stopped for spend."},"dailyBudgetUsd":{"type":"number","default":5},"maxConcurrentJobs":{"type":"integer","default":1,"minimum":1,"maximum":20},"maxAttempts":{"type":"integer","default":2,"minimum":1,"maximum":10}}},"approvals":{"type":"object","description":"Which actions wait for a person. Each is `require` (default) or `allow`.","properties":{"delete":{"type":"string","enum":["require","allow"]},"bulkSend":{"type":"string","enum":["require","allow"],"description":"Messages to more than a few people at once."},"calls":{"type":"string","enum":["require","allow"],"description":"Placing a phone call."},"money":{"type":"string","enum":["require","allow"],"description":"Refunds, payments, charges, credits."},"moneyThresholdUsd":{"type":"number","description":"With money on allow, amounts above this still need an OK."},"usersAndPermissions":{"type":"string","enum":["require","allow"]}}},"voice":{"type":"object","description":"How it sounds on the phone. Calls to a number assigned to it are answered by it.","properties":{"enabled":{"type":"boolean","default":true},"voice":{"type":"string","description":"An ElevenLabs voice_id or an OpenAI voice name, from the voice list."},"platform":{"type":"string","enum":["elevenlabs","openai-realtime"],"description":"Set with the voice."},"voiceName":{"type":"string"},"language":{"type":"string","default":"en"},"greeting":{"type":"string","maxLength":300},"eagerness":{"type":"string","enum":["low","medium","high"],"default":"medium"},"speakingSpeed":{"type":"number","minimum":0.7,"maximum":1.2,"description":"How fast it talks; blank or 1 = the voice's own pace."},"tools":{"type":"array","items":{"type":"string"},"description":"What it can do during a call."}}},"status":{"type":"string","enum":["draft","active","paused"],"readOnly":true,"description":"Changed only by switch-on / pause / shut-down."},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true,"description":"Its login identity: `<name>@<orgId>.ai-employee.local`."},"template":{"type":"string","readOnly":true}}}}},"access":{"type":"object","nullable":true,"properties":{"userId":{"type":"string"},"email":{"type":"string"},"groups":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"locked":{"type":"boolean"}}},"contact":{"type":"object","properties":{"email":{"type":"string"},"phones":{"type":"array","items":{"type":"object","additionalProperties":true}},"numbers":{"type":"object","additionalProperties":true}}},"team":{"type":"object","nullable":true,"properties":{"workspaceId":{"type":"string"},"title":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/pause","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Status `paused`: it takes no work and its login is locked, so a runtime holding a token is stopped at its next call. A running session is not ended — use shut-down for that.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/pause (name: string, body) -> The employee (as GET)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /ai-employees/{name}/shut-down`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Reviewing its refunds"}}}}}},"/ai-employees/{name}/shut-down":{"post":{"operationId":"AIEmployeeController_shutDown","summary":"Shut it down","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"Provider status plus `requeued` (jobs put back)","content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"}}},"status":{"type":"object","properties":{"exists":{"type":"boolean","nullable":true},"state":{"type":"string"},"detail":{"type":"string"},"checkedAt":{"type":"string"}}},"connection":{"type":"object","additionalProperties":true,"description":"What the agent itself last reported (hello / briefing / lease / step)."},"requeued":{"type":"integer"}}}}}},"400":{"description":"There is no connection to the provider \"external\" yet. — The employee's `runtime.provider` has no connection here.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"There is no connection to the provider \"external\" yet.","path":"/ai-employees/{name}/shut-down","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/shut-down","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"The session manager did not stop it. — The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"The session manager did not stop it.","path":"/ai-employees/{name}/shut-down","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["AI Employees"],"description":"Harder than pause: its running session is ended now, what it had taken goes back in the queue, and it is paused (login locked). Switch it on to start it again.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/shut-down (name: string) -> Provider status plus `requeued` (jobs put back)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | NO_PROVIDER | There is no connection to the provider \"external\" yet. | The employee's `runtime.provider` has no connection here. | — |\n| `502` | PROVIDER_ERROR | The session manager did not stop it. | The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/{name}/restart":{"post":{"operationId":"AIEmployeeController_restart","summary":"Restart it","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"Provider status plus `requeued` and `forgotten`","content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"}}},"status":{"type":"object","properties":{"exists":{"type":"boolean","nullable":true},"state":{"type":"string"},"detail":{"type":"string"},"checkedAt":{"type":"string"}}},"connection":{"type":"object","additionalProperties":true,"description":"What the agent itself last reported (hello / briefing / lease / step)."},"requeued":{"type":"integer"},"forgotten":{"type":"integer"}}}}}},"400":{"description":"There is no connection to the provider \"external\" yet. — The employee's `runtime.provider` has no connection here.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"There is no connection to the provider \"external\" yet.","path":"/ai-employees/{name}/restart","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/restart","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"The session manager did not restart it. — The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"The session manager did not restart it.","path":"/ai-employees/{name}/restart","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["AI Employees"],"description":"Ends its session now and starts a fresh one that reads its briefing anew; what it had taken goes back in the queue. `forget: true` also drops the conversations it would resume.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/restart (name: string, body) -> Provider status plus `requeued` and `forgotten`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | NO_PROVIDER | There is no connection to the provider \"external\" yet. | The employee's `runtime.provider` has no connection here. | — |\n| `502` | PROVIDER_ERROR | The session manager did not restart it. | The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"forget":{"type":"boolean","default":false}}},"example":{"forget":true}}}}}},"/ai-employees/{name}/memory":{"get":{"operationId":"AIEmployeeController_memory","summary":"Get its working memory","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"200":{"description":"{ notes, rememberedSessions, canForgetSessions }","content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","nullable":true},"author":{"type":"string"},"text":{"type":"string"}}}},"rememberedSessions":{"nullable":true,"description":"Conversations its provider would resume; null when the provider does not say."},"canForgetSessions":{"type":"boolean"}}}}}},"400":{"description":"There is no connection to the provider \"external\" yet. — The employee's `runtime.provider` has no connection here.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"There is no connection to the provider \"external\" yet.","path":"/ai-employees/{name}/memory","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/memory","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Ava has no user, so it keeps no notes. — The employee has no user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Ava has no user, so it keeps no notes.","path":"/ai-employees/{name}/memory","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"The notes it wrote for itself (newest first) and, when its provider says, the conversations it would resume.\n\n#### Signature\n\n```http\nGET /ai-employees/{name}/memory (name: string) -> { notes, rememberedSessions, canForgetSessions }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | NO_PROVIDER | There is no connection to the provider \"external\" yet. | The employee's `runtime.provider` has no connection here. | — |\n| `409` | NO_USER | Ava has no user, so it keeps no notes. | The employee has no user. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/{name}/memory/notes/remove":{"post":{"operationId":"AIEmployeeController_forgetNotes","summary":"Remove notes from its memory","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"Its notes after the change","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"Say which note (its time). — `at` is not a date.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say which note (its time).","path":"/ai-employees/{name}/memory/notes/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/memory/notes/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"`at` removes the note written at that time; without it, all its notes are cleared.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/memory/notes/remove (name: string, body) -> Its notes after the change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | NOTE_TIME_INVALID | Say which note (its time). | `at` is not a date. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"at":{"type":"string","format":"date-time","description":"The note's time, from GET memory."}}},"example":{"at":"2026-09-28T14:03:11.000Z"}}}}}},"/ai-employees/{name}/memory/forget-sessions":{"post":{"operationId":"AIEmployeeController_forgetSessions","summary":"Drop the conversations it would resume","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"Its memory (as GET) plus `forgotten`","content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","nullable":true},"author":{"type":"string"},"text":{"type":"string"}}}},"rememberedSessions":{"nullable":true,"description":"Conversations its provider would resume; null when the provider does not say."},"canForgetSessions":{"type":"boolean"},"forgotten":{"type":"integer"}}}}}},"400":{"description":"There is no connection to the provider \"external\" yet. — The employee's `runtime.provider` has no connection here.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"There is no connection to the provider \"external\" yet.","path":"/ai-employees/{name}/memory/forget-sessions","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/memory/forget-sessions","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"The session manager did not answer. — The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"The session manager did not answer.","path":"/ai-employees/{name}/memory/forget-sessions","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["AI Employees"],"description":"So its next job starts clean.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/memory/forget-sessions (name: string) -> Its memory (as GET) plus `forgotten`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | NO_PROVIDER | There is no connection to the provider \"external\" yet. | The employee's `runtime.provider` has no connection here. | — |\n| `502` | PROVIDER_ERROR | The session manager did not answer. | The runtime provider (session manager) refused or did not answer; the text is its reason when it gave one. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/ai-employees/{name}/work":{"post":{"operationId":"AIEmployeeController_enqueue","summary":"Give an employee work","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"The employee's handle (`ai_employee.name`).","example":"ava"}],"responses":{"201":{"description":"The queued work item","content":{"application/json":{"schema":{"type":"object","description":"A work item (`ai_employee_work`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"employee":{"type":"string"},"title":{"type":"string"},"instructions":{"type":"string"},"source":{"type":"string","enum":["assigned","ping","direct","message","call","config"]},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"},"thread":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}},"requestedBy":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"]},"status":{"type":"string","enum":["queued","in_progress","waiting_approval","done","failed","cancelled"]},"lease":{"type":"object","nullable":true,"properties":{"worker":{"type":"string"},"until":{"type":"string","format":"date-time"}}},"attempts":{"type":"integer"},"steps":{"type":"array","description":"The timeline, in order.","items":{"type":"object","properties":{"at":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"detail":{"type":"object","additionalProperties":true}}}},"pendingApproval":{"type":"object","nullable":true,"properties":{"action":{"type":"string"},"summary":{"type":"string"},"detail":{"type":"object","additionalProperties":true},"amountUsd":{"type":"number"},"requestedAt":{"type":"string"}}},"decisions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"summary":{"type":"string"},"decision":{"type":"string","enum":["approved","rejected"]},"by":{"type":"string"},"note":{"type":"string"},"at":{"type":"string"}}}},"result":{"type":"string"},"error":{"type":"string"},"costUsd":{"type":"number"},"startedAt":{"type":"string"},"finishedAt":{"type":"string"}}}}}}}},"400":{"description":"Say what the work is. — No title and no instructions.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say what the work is.","path":"/ai-employees/{name}/work","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No AI employee \"ava\" — No employee (templates excluded) has that handle.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No AI employee \"ava\"","path":"/ai-employees/{name}/work","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["AI Employees"],"description":"Queues a direct request. The title defaults to the first line of the instructions. Attachments are records (`kind: record`, datatype, id) and documents (`kind: file`, path or url), each with a comment on what it is for; a record is described from the record itself.\n\n#### Signature\n\n```http\nPOST /ai-employees/{name}/work (name: string, body) -> The queued work item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `RootAdmin`, `RootSystem`, `RootPowerUser`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AI_EMPLOYEE_NOT_FOUND | No AI employee \"ava\" | No employee (templates excluded) has that handle. | — |\n| `400` | WORK_TITLE_REQUIRED | Say what the work is. | No title and no instructions. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":140},"instructions":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high"],"default":"normal"},"ref":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"label":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["record","file"]},"datatype":{"type":"string","description":"record: its collection."},"id":{"type":"string","description":"record: its id."},"label":{"type":"string","description":"For a record the server reads its own label; the caller's is not trusted."},"path":{"type":"string","description":"file: where it is stored."},"url":{"type":"string"},"mime":{"type":"string"},"comment":{"type":"string","description":"What it is for."}}}}}},"example":{"title":"Call back the Garcia party","instructions":"They asked to move Friday's booking to 8pm. Confirm by text.","priority":"high","attachments":[{"kind":"record","datatype":"reservation","id":"6710c0ffee0000000000f00d","comment":"The booking"}]}}}}}},"/business-made/insights":{"get":{"operationId":"InsightsController_snapshot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period (ISO date). Default: the first day of this month.","example":"2026-09-01"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period (ISO date). Default: now.","example":"2026-09-30"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Narrow to one location.","example":"harbor-grill"}],"responses":{"200":{"description":"`{ period:{from,to}, businessLocationId, money, sales, floor, workflow, bookings, team, scheduling, payroll, books, tax, inventory, customers, timeoff, giftcards, generatedAt, alerts:[{severity: \"bad\" | \"warn\", label, to}], deltas }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Business snapshot","description":"One snapshot of the business for the period (default month-to-date): money, sales, the floor, workflow, bookings, team, scheduling, payroll, books, tax, inventory, customers, time off and gift cards. `alerts` lists what needs attention (tax filings due, books out of balance, gift card books not matching the cards, value expiring, …) with a link to the dashboard that explains it, and `deltas` compares each figure with the same-length period just before.\n\n#### Signature\n\n```http\nGET /business-made/insights (from?: string, to?: string, businessLocationId?: string) -> `{ period:{from,to}, businessLocationId, money, sales, floor, workflow, bookings, team, scheduling, payroll, books, tax, inventory, customers, timeoff, giftcards, generatedAt, alerts:[{severity: \"bad\" \\| \"warn\", label, to}], deltas }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/insights/{category}`","tags":["Business Made · Insights"]}},"/business-made/insights/{category}":{"get":{"operationId":"InsightsController_detail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"category","required":true,"in":"path","schema":{"type":"string","enum":["money","sales","floor","workflow","bookings","team","scheduling","payroll","books","tax","inventory","customers","timeoff","giftcards"]},"example":"money"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"Start of the period (ISO date). Default: the first day of this month.","example":"2026-09-01"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"End of the period (ISO date). Default: now.","example":"2026-09-30"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Narrow to one location.","example":"harbor-grill"}],"responses":{"200":{"description":"`{ category, period, businessLocationId, generatedAt, stats:[{label, value, previous?, delta?}], panels:[{…, back}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown dashboard \"cash\". Expected one of: money, sales, floor, workflow, bookings, team, scheduling, payroll, books, tax, inventory, customers, timeoff, giftcards — `category` is not one of the dashboards.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown dashboard \"cash\". Expected one of: money, sales, floor, workflow, bookings, team, scheduling, payroll, books, tax, inventory, customers, timeoff, giftcards","path":"/business-made/insights/{category}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"One insights dashboard","description":"The deep view for one hub category, render-ready: headline `stats` and declared `panels` chosen for the shape of their data (empty panels are dropped). Every panel carries the rows behind it (`back`) so any number can be traced to its records. Each numeric stat carries `previous` and `delta` (% change) against the same-length period just before.\n\n#### Signature\n\n```http\nGET /business-made/insights/{category} (category: string, from?: string, to?: string, businessLocationId?: string) -> `{ category, period, businessLocationId, generatedAt, stats:[{label, value, previous?, delta?}], panels:[{…, back}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_DASHBOARD | Unknown dashboard \"cash\". Expected one of: money, sales, floor, workflow, bookings, team, scheduling, payroll, books, tax, inventory, customers, timeoff, giftcards | `category` is not one of the dashboards. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Insights"]}},"/business-made/employees/overview":{"get":{"operationId":"EmployeeController_overview","summary":"HR overview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Location; `all` = the whole business.","example":"harbor-grill"}],"responses":{"200":{"description":"`{ location, people:{headcount, onLeave, leftLast12Months, unplaced, locations}, recentHires:[{sk, name, initials, jobTitle, department, status, …}], timeOff:{state, …} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"The HR home figures in one read: people counts (headcount of active people, on leave, left in the last 12 months, unplaced), the five most recent hires, and today's time off when the Leave module is ready (who is off, decisions waiting, paid time owed). A location narrows it; in a one-location business people not yet placed anywhere count there.\n\n#### Signature\n\n```http\nGET /business-made/employees/overview (businessLocationId?: string) -> `{ location, people:{headcount, onLeave, leftLast12Months, unplaced, locations}, recentHires:[{sk, name, initials, jobTitle, department, status, …}], timeOff:{state, …} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/employees":{"get":{"operationId":"EmployeeController_getEmployees","summary":"List employees","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"department","in":"query","required":false,"schema":{"type":"string"},"example":"engineering"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"example":"active"},{"name":"page","in":"query","required":false,"schema":{"type":"integer"},"example":1},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Employees","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Lists employees with arbitrary query filtering. The dedicated filter routes below cover the common cases more legibly.\n\n#### Signature\n\n```http\nGET /business-made/employees (department?: string, status?: string, page?: integer, pageSize?: integer) -> Employees\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Employee records contain personal data — restrict who can reach this.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/{id}`"},"post":{"operationId":"EmployeeController_createEmployee","summary":"Create an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created employee","content":{"application/json":{"schema":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Employee ID already exists — Another employee already uses that `employeeId`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Employee ID already exists","path":"/business-made/employees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Creates an employee record. The business `employeeId` must be unique within the org — a collision is refused with a `409` naming the id, rather than creating a second record for the same person.\n\n#### Signature\n\n```http\nPOST /business-made/employees (body) -> The created employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | EMPLOYEE_ID_EXISTS | Employee ID already exists | Another employee already uses that `employeeId`. | The body echoes the `employeeId`. Look up the existing record rather than creating a duplicate. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}`","requestBody":{"description":"The employee to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"E-00412","firstName":"Ada","lastName":"Lovelace","email":"ada@acme.com","department":"engineering","startDate":"2026-09-01"}}}}}},"/business-made/employees/{id}":{"get":{"operationId":"EmployeeController_getEmployee","summary":"Get an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The employee","content":{"application/json":{"schema":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/employees/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Fetches one employee record.\n\n#### Signature\n\n```http\nGET /business-made/employees/{id} (id: string) -> The employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/update`"},"delete":{"operationId":"EmployeeController_deleteEmployee","summary":"Delete an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Deletes an employee record outright.\n\nFor someone who has left, **terminate them instead** — that preserves the employment history, which is usually a legal retention requirement. Deletion is for a record created in error.\n\n#### Signature\n\n```http\nDELETE /business-made/employees/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Destroys employment history. Termination is almost always the correct action.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/terminate`"}},"/business-made/employees/update":{"post":{"operationId":"EmployeeController_updateEmployee","summary":"Update an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated employee","content":{"application/json":{"schema":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}},"400":{"description":"The employee id (sk) is required — Neither `sk` nor `id` is in the body.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The employee id (sk) is required","path":"/business-made/employees/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee <sk> not found — No employee has that sk.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee <sk> not found","path":"/business-made/employees/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Updates an employee record. Status changes and termination have dedicated endpoints that record the transition — use those rather than writing `status` here.\n\n#### Signature\n\n```http\nPOST /business-made/employees/update (body) -> The updated employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SK_REQUIRED | The employee id (sk) is required | Neither `sk` nor `id` is in the body. | — |\n| `404` | EMPLOYEE_NOT_FOUND | Employee <sk> not found | No employee has that sk. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/status`","requestBody":{"description":"The employee to update, including its id.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"EMP-4821","department":"platform","location":"london"}}}}}},"/business-made/employees/department/{department}":{"get":{"operationId":"EmployeeController_getEmployeesByDepartment","summary":"List employees by department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"department","required":true,"in":"path","schema":{"type":"string"},"description":"Department code or id.","example":"engineering"}],"responses":{"200":{"description":"Employees","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Employees in one department.\n\n#### Signature\n\n```http\nGET /business-made/employees/department/{department} (department: string) -> Employees\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/location/{location}`"}},"/business-made/employees/location/{location}":{"get":{"operationId":"EmployeeController_getEmployeesByLocation","summary":"List employees by location","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"location","required":true,"in":"path","schema":{"type":"string"},"description":"Location code.","example":"london"}],"responses":{"200":{"description":"Employees","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Employees at one location.\n\n#### Signature\n\n```http\nGET /business-made/employees/location/{location} (location: string) -> Employees\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/status/active`"}},"/business-made/employees/status/active":{"get":{"operationId":"EmployeeController_getActiveEmployees","summary":"List active employees","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active employees","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Currently employed staff — the headcount denominator, excluding terminated and on-leave records.\n\n#### Signature\n\n```http\nGET /business-made/employees/status/active () -> Active employees\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/reports/headcount`"}},"/business-made/employees/supervisor/{supervisorId}":{"get":{"operationId":"EmployeeController_getEmployeesBySupervisor","summary":"List an employee's direct reports","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"supervisorId","required":true,"in":"path","schema":{"type":"string"},"description":"Supervisor employee id.","example":"EMP-4001"}],"responses":{"200":{"description":"Direct reports","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Everyone reporting to one supervisor — direct reports only, not the whole tree beneath them.\n\n#### Signature\n\n```http\nGET /business-made/employees/supervisor/{supervisorId} (supervisorId: string) -> Direct reports\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- One level deep. Walk it recursively for a full org tree, or use the org chart endpoints.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/charts`"}},"/business-made/employees/skill/{skillName}":{"get":{"operationId":"EmployeeController_getEmployeesBySkill","summary":"Find employees by skill","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"skillName","required":true,"in":"path","schema":{"type":"string"},"description":"Skill name.","example":"kubernetes"},{"name":"minProficiency","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Employees with the skill","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Everyone recorded as holding a skill — the staffing lookup when a project needs a particular competency.\n\n#### Signature\n\n```http\nGET /business-made/employees/skill/{skillName} (skillName: string) -> Employees with the skill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/skills`"}},"/business-made/employees/{id}/status":{"post":{"operationId":"EmployeeController_updateEmployeeStatus","summary":"Change an employee status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The updated employee","content":{"application/json":{"schema":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Sets an employee's status with a reason — moving someone to leave, or back to active. Termination is separate because it does more than set a field.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/status (id: string, body) -> The updated employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/terminate`","requestBody":{"description":"The new status.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","example":"on-leave"},"reason":{"type":"string","example":"Parental leave"}}},"example":{"status":"on-leave","reason":"Parental leave"}}}}}},"/business-made/employees/{id}/terminate":{"post":{"operationId":"EmployeeController_terminateEmployee","summary":"Terminate an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The terminated employee","content":{"application/json":{"schema":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/terminate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Records the end of employment. Unlike a plain status change this is the formal termination — it is what the offboarding process, final pay and access revocation hang off.\n\nThe record is kept, which is the point: employment history has to survive the person leaving.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/terminate (id: string, body) -> The terminated employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Terminating does not revoke system access or issue final pay — those are offboarding steps.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding`\n- `POST /business-made/offboarding/{id}/revoke-access`","requestBody":{"description":"Termination details.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"terminationDate":"2026-09-30","reason":"Resignation","rehireEligible":true}}}}}},"/business-made/employees/{id}/time-off/request":{"post":{"operationId":"EmployeeController_requestTimeOff","summary":"Request time off","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The created request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/time-off/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Raises a time-off request for an employee. It waits for approval — nothing is deducted from a balance until approved.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/time-off/request (id: string, body) -> The created request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/time-off/{requestId}/approve`","requestBody":{"description":"The request.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"type":"annual","startDate":"2026-10-05","endDate":"2026-10-09","note":"Half-term"}}}}}},"/business-made/employees/{id}/time-off/{requestId}/approve":{"post":{"operationId":"EmployeeController_approveTimeOff","summary":"Approve a time-off request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Time-off request id.","example":"TOR-4821"}],"responses":{"201":{"description":"The approved request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/time-off/{requestId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Approves a pending request. This is what commits the absence and draws down the balance.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/time-off/{requestId}/approve (id: string, requestId: string, body) -> The approved request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/time-off/{requestId}/reject`","requestBody":{"description":"Optional approval note.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Approved — cover arranged"}}}}}},"/business-made/employees/{id}/time-off/{requestId}/reject":{"post":{"operationId":"EmployeeController_rejectTimeOff","summary":"Reject a time-off request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Time-off request id.","example":"TOR-4821"}],"responses":{"201":{"description":"The rejected request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/time-off/{requestId}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Declines a pending request, with a reason the employee will see.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/time-off/{requestId}/reject (id: string, requestId: string, body) -> The rejected request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/{id}/time-off`","requestBody":{"description":"Why it was rejected.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Two others already off that week"}}}}}},"/business-made/employees/{id}/time-off":{"get":{"operationId":"EmployeeController_getTimeOffRequests","summary":"Get an employee's time off","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Time-off records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"An employee's time-off requests and their outcomes.\n\n#### Signature\n\n```http\nGET /business-made/employees/{id}/time-off (id: string) -> Time-off records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/time-off/request`"}},"/business-made/employees/{id}/reviews":{"post":{"operationId":"EmployeeController_addPerformanceReview","summary":"Add a performance review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The created review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/reviews","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Records a performance review against an employee.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/reviews (id: string, body) -> The created review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Reviews are sensitive personal data — restrict read access as tightly as write access.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/{id}/reviews`","requestBody":{"description":"The review.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"period":"2026-H1","rating":4,"summary":"Strong delivery on the platform migration"}}}}},"get":{"operationId":"EmployeeController_getPerformanceReviews","summary":"Get an employee's reviews","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"Reviews","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"An employee's performance review history.\n\n#### Signature\n\n```http\nGET /business-made/employees/{id}/reviews (id: string) -> Reviews\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/reviews`"}},"/business-made/employees/{id}/training":{"post":{"operationId":"EmployeeController_addTraining","summary":"Assign training","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The assigned training","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/training","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Assigns a training course to an employee. Where the training carries an expiry, it feeds the expired-training report.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/training (id: string, body) -> The assigned training\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/training/{trainingId}/complete`","requestBody":{"description":"The training to assign.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Fire safety","dueDate":"2026-10-31","expiresAfterMonths":12}}}}}},"/business-made/employees/{id}/training/{trainingId}/complete":{"post":{"operationId":"EmployeeController_completeTraining","summary":"Complete training","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"trainingId","required":true,"in":"path","schema":{"type":"string"},"description":"Training record id.","example":"TRN-4821"}],"responses":{"201":{"description":"The completed training","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/training/{trainingId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Marks assigned training as completed, which starts its expiry clock where one applies.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/training/{trainingId}/complete (id: string, trainingId: string, body) -> The completed training\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/training/expired`","requestBody":{"description":"Completion details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"completedDate":"2026-09-15","score":92}}}}}},"/business-made/employees/training/expired":{"get":{"operationId":"EmployeeController_getExpiredTraining","summary":"List expired training","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Expired training records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Training that has lapsed across the workforce — the compliance worklist.\n\nFor safety or regulatory training this is the report that matters: an employee with lapsed certification may not lawfully be doing the work.\n\n#### Signature\n\n```http\nGET /business-made/employees/training/expired () -> Expired training records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/training`"}},"/business-made/employees/{id}/skills":{"post":{"operationId":"EmployeeController_addSkill","summary":"Add a skill","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The updated employee","content":{"application/json":{"schema":{"type":"object","description":"An employee record.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"employeeId":{"type":"string","description":"Business employee number. Must be unique in the org.","example":"E-00412"},"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@acme.com"},"department":{"type":"string","example":"engineering"},"location":{"type":"string","example":"london"},"position":{"type":"string","example":"POS-eng-lead"},"supervisorId":{"type":"string","example":"EMP-4001"},"status":{"type":"string","description":"`active`, `on-leave`, `terminated`.","example":"active"},"startDate":{"type":"string","format":"date"},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/skills","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Records a skill against an employee, making them findable through the skill lookup.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/skills (id: string, body) -> The updated employee\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/skill/{skillName}`","requestBody":{"description":"The skill to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"kubernetes","level":"advanced"}}}}}},"/business-made/employees/{id}/onboarding/start":{"post":{"operationId":"EmployeeController_startOnboarding","summary":"Start onboarding for an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/employees/{id}/onboarding/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Starts onboarding with an explicit task list.\n\nThe dedicated onboarding controller (`/business-made/onboarding`) drives the same process from a configured template instead. Use this when the tasks are being supplied directly.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/onboarding/start (id: string, body) -> The onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | Check the id with `GET /business-made/employees`. The body carries `code` and the `employeeId` it tried. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}`","requestBody":{"description":"The onboarding tasks.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["tasks"],"properties":{"tasks":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The tasks to complete."}}},"example":{"tasks":[{"key":"contract","title":"Sign contract"},{"key":"laptop","title":"Issue laptop"}]}}}}}},"/business-made/employees/{id}/onboarding/{taskId}/complete":{"post":{"operationId":"EmployeeController_completeOnboardingTask","summary":"Complete an onboarding task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Onboarding task id.","example":"TSK-contract"}],"responses":{"201":{"description":"The updated onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Onboarding not found — No onboarding matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Onboarding not found","path":"/business-made/employees/{id}/onboarding/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Marks one onboarding task complete for an employee.\n\n#### Signature\n\n```http\nPOST /business-made/employees/{id}/onboarding/{taskId}/complete (id: string, taskId: string) -> The updated onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ONBOARDING_NOT_FOUND | Onboarding not found | No onboarding matches. | The error body carries `code: \"ONBOARDING_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}/items/{itemKey}/complete`"}},"/business-made/employees/reports/headcount":{"get":{"operationId":"EmployeeController_getHeadcount","summary":"Get the headcount report","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Headcount report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Headcount broken down by department, location and status — the establishment figures.\n\n#### Signature\n\n```http\nGET /business-made/employees/reports/headcount () -> Headcount report\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/metrics`"}},"/business-made/employees/reports/anniversaries":{"get":{"operationId":"EmployeeController_getUpcomingAnniversaries","summary":"Get work anniversaries","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"days","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Upcoming anniversaries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Upcoming service anniversaries — what an internal comms or recognition programme reads.\n\n#### Signature\n\n```http\nGET /business-made/employees/reports/anniversaries () -> Upcoming anniversaries\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/reports/birthdays`"}},"/business-made/employees/reports/birthdays":{"get":{"operationId":"EmployeeController_getUpcomingBirthdays","summary":"Get upcoming birthdays","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"days","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Upcoming birthdays","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Upcoming employee birthdays. Dates of birth are personal data, and some people prefer them not shared — check consent before surfacing this internally.\n\n#### Signature\n\n```http\nGET /business-made/employees/reports/birthdays () -> Upcoming birthdays\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Exposes personal data that not everyone wants published.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/reports/anniversaries`"}},"/business-made/timesheets":{"get":{"operationId":"TimesheetController_getTimesheets","summary":"List timesheets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Timesheets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Timesheets across the org.\n\n#### Signature\n\n```http\nGET /business-made/timesheets () -> Timesheets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/status/pending`"},"post":{"operationId":"TimesheetController_createTimesheet","summary":"Create a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Timesheet already exists for this pay period — The employee already has a timesheet for that period.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Timesheet already exists for this pay period","path":"/business-made/timesheets","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Opens a timesheet for an employee and pay period. **One per employee per period** — a second is refused with a `409` that names the existing one.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets (body) -> The created timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | TIMESHEET_EXISTS | Timesheet already exists for this pay period | The employee already has a timesheet for that period. | The body identifies the existing timesheet — add entries to it instead. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/entries`","requestBody":{"description":"The timesheet to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","payPeriodStart":"2026-09-01","payPeriodEnd":"2026-09-15"}}}}}},"/business-made/timesheets/{id}":{"get":{"operationId":"TimesheetController_getTimesheet","summary":"Get a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/timesheets/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Fetches one timesheet with its entries and totals.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/{id} (id: string) -> The timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/submit`"},"delete":{"operationId":"TimesheetController_deleteTimesheet","summary":"Delete a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only draft or rejected timesheets can be deleted — The timesheet is submitted or approved; the body carries its `status`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only draft or rejected timesheets can be deleted","path":"/business-made/timesheets/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Deletes a draft or rejected timesheet. A submitted or approved one is refused — it is the record payroll is calculated from; reopen a rejected one and correct it instead.\n\n#### Signature\n\n```http\nDELETE /business-made/timesheets/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CANNOT_DELETE | Only draft or rejected timesheets can be deleted | The timesheet is submitted or approved; the body carries its `status`. | Leave it, or have it rejected first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/reopen`"}},"/business-made/timesheets/update":{"post":{"operationId":"TimesheetController_updateTimesheet","summary":"Update a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Updates a timesheet's own fields. Entries are managed through the entry endpoints. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/update (body) -> The updated timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/entries`","requestBody":{"description":"The timesheet to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"TS-4821","data":{"note":"Includes on-call hours"}}}}}}},"/business-made/timesheets/employee/{employeeId}":{"get":{"operationId":"TimesheetController_getTimesheetsByEmployee","summary":"Get an employee's timesheets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"Timesheets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"One employee's timesheet history.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/employee/{employeeId} (employeeId: string) -> Timesheets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/summary/{employeeId}`"}},"/business-made/timesheets/status/pending":{"get":{"operationId":"TimesheetController_getPendingTimesheets","summary":"List pending timesheets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Pending timesheets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Timesheets submitted and awaiting approval — the approval queue before a payroll run.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/status/pending () -> Pending timesheets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/approve`"}},"/business-made/timesheets/status/{status}":{"get":{"operationId":"TimesheetController_getTimesheetsByStatus","summary":"List timesheets by status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":true,"in":"path","schema":{"type":"string"},"description":"Timesheet status.","example":"approved"}],"responses":{"200":{"description":"Timesheets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Timesheets in any given status — the general form of the pending list.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/status/{status} (status: string) -> Timesheets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/status/pending`"}},"/business-made/timesheets/{id}/submit":{"post":{"operationId":"TimesheetController_submitTimesheet","summary":"Submit a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The submitted timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only draft timesheets can be submitted — The timesheet is not a draft.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only draft timesheets can be submitted","path":"/business-made/timesheets/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Sends a timesheet for approval. **An empty timesheet is refused** — submitting nothing would otherwise pass silently into payroll as zero hours.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/{id}/submit (id: string) -> The submitted timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n| `400` | INVALID_STATUS | Only draft timesheets can be submitted | The timesheet is not a draft. | Check the timesheet status first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/approve`"}},"/business-made/timesheets/{id}/approve":{"post":{"operationId":"TimesheetController_approveTimesheet","summary":"Approve a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The approved timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only submitted timesheets can be approved — The timesheet is not submitted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only submitted timesheets can be approved","path":"/business-made/timesheets/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Approves a submitted timesheet, making its hours available to payroll. Correcting one after approval requires reopening it.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/{id}/approve (id: string, body) -> The approved timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n| `400` | INVALID_STATUS | Only submitted timesheets can be approved | The timesheet is not submitted. | Check the timesheet status first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/reopen`","requestBody":{"description":"Optional approval note.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Overtime verified against the schedule"}}}}}},"/business-made/timesheets/{id}/reject":{"post":{"operationId":"TimesheetController_rejectTimesheet","summary":"Reject a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The rejected timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only submitted timesheets can be rejected — The timesheet is not submitted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only submitted timesheets can be rejected","path":"/business-made/timesheets/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Sends a timesheet back with a reason, so the employee can correct and resubmit it.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/{id}/reject (id: string, body) -> The rejected timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n| `400` | INVALID_STATUS | Only submitted timesheets can be rejected | The timesheet is not submitted. | Check the timesheet status first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/submit`","requestBody":{"description":"Why it was rejected.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Thursday hours do not match the rota"}}}}}},"/business-made/timesheets/{id}/reopen":{"post":{"operationId":"TimesheetController_reopenTimesheet","summary":"Reopen a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The reopened timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only rejected timesheets can be reopened — The timesheet is not rejected.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only rejected timesheets can be reopened","path":"/business-made/timesheets/{id}/reopen","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/{id}/reopen","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Puts a **rejected** timesheet back to draft so it can be corrected and submitted again. An approved timesheet cannot be reopened here.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/{id}/reopen (id: string) -> The reopened timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n| `400` | INVALID_STATUS | Only rejected timesheets can be reopened | The timesheet is not rejected. | Check the timesheet status first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/approve`"}},"/business-made/timesheets/{id}/entries":{"post":{"operationId":"TimesheetController_addTimeEntry","summary":"Add a time entry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Can only add entries to draft timesheets — The timesheet is not a draft (the message names the operation).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Can only add entries to draft timesheets","path":"/business-made/timesheets/{id}/entries","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/{id}/entries","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Adds an entry to a timesheet — hours worked on a day, optionally against a project or cost code.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/{id}/entries (id: string, body) -> The updated timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n| `400` | TIMESHEET_NOT_EDITABLE | Can only add entries to draft timesheets | The timesheet is not a draft (the message names the operation). | Reopen a rejected timesheet first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/entries/{entryId}`","requestBody":{"description":"The entry to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"date":"2026-09-03","hours":7.5,"project":"PLATFORM","note":"Migration work"}}}}}},"/business-made/timesheets/{id}/entries/{entryId}":{"post":{"operationId":"TimesheetController_updateTimeEntry","summary":"Update a time entry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"entryId","required":true,"in":"path","schema":{"type":"string"},"description":"Entry id.","example":"TSE-4821"}],"responses":{"201":{"description":"The updated timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Can only update entries in draft timesheets — The timesheet is not a draft (the message names the operation).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Can only update entries in draft timesheets","path":"/business-made/timesheets/{id}/entries/{entryId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/{id}/entries/{entryId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Updates one entry on a timesheet.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/{id}/entries/{entryId} (id: string, entryId: string, body) -> The updated timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n| `400` | TIMESHEET_NOT_EDITABLE | Can only update entries in draft timesheets | The timesheet is not a draft (the message names the operation). | Reopen a rejected timesheet first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/timesheets/{id}/entries/{entryId}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"hours":8}}}}},"delete":{"operationId":"TimesheetController_removeTimeEntry","summary":"Delete a time entry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"entryId","required":true,"in":"path","schema":{"type":"string"},"description":"Entry id.","example":"TSE-4821"}],"responses":{"200":{"description":"The updated timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Can only remove entries from draft timesheets — The timesheet is not a draft (the message names the operation).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Can only remove entries from draft timesheets","path":"/business-made/timesheets/{id}/entries/{entryId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/{id}/entries/{entryId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Removes an entry from a timesheet and recalculates its totals.\n\n#### Signature\n\n```http\nDELETE /business-made/timesheets/{id}/entries/{entryId} (id: string, entryId: string) -> The updated timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n| `400` | TIMESHEET_NOT_EDITABLE | Can only remove entries from draft timesheets | The timesheet is not a draft (the message names the operation). | Reopen a rejected timesheet first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/{id}/entries`"}},"/business-made/timesheets/clock-in/{employeeId}":{"post":{"operationId":"TimesheetController_clockIn","summary":"Clock in","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The clock-in record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Already clocked in — The employee has an open clock-in; the body carries its `entryId` and `startTime`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Already clocked in","path":"/business-made/timesheets/clock-in/{employeeId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/business-made/timesheets/clock-in/{employeeId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't clock in: <requirement titles>. — A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"clockIn","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Starts a work session for an employee on their current timesheet. `capture` is what the device could see (location, photo) — where the org requires a geofence or a photo and it does not match, the clock-in still goes through and is flagged for review (GET /business-made/timesheets/clock-events/needs-review). A readiness block (missing required training or documents) can be overridden by a manager with `override`.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/clock-in/{employeeId} (employeeId: string, body) -> The clock-in record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- ClockIn gate, judged against the station/location of the shift the person is scheduled on now (unscheduled = no station). Block: 423 `{ message, reason: \"readiness_block\", blocking[], trainingAtClockIn[], shift, canOverride, override }`; a manager signed in here resends with `override`. Success adds `readiness: { effect, warnings[], reasons[], overridden }` (null when nothing applies) and `trainingAtClockIn[]`.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ALREADY_CLOCKED_IN | Already clocked in | The employee has an open clock-in; the body carries its `entryId` and `startTime`. | Clock out first. |\n| `423` | READINESS_BLOCK | <name> can't clock in: <requirement titles>. | A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/clock-out/{employeeId}`","requestBody":{"description":"Optional clock-in details.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"taskId":{"type":"string"},"notes":{"type":"string"},"capture":{"type":"object","additionalProperties":true,"description":"Location / photo captured by the device."},"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}},"example":{"notes":"Opening shift","capture":{"lat":51.5072,"lng":-0.1276}}}}}}},"/business-made/timesheets/clock-out/{employeeId}":{"post":{"operationId":"TimesheetController_clockOut","summary":"Clock out","description":"Ends the current work session and writes the hours to the timesheet. Refused when the employee is not clocked in, so a stray clock-out cannot create a phantom entry.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/clock-out/{employeeId} (employeeId: string, body) -> The completed session\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Someone who forgets to clock out leaves an open session — the on-the-clock report is where those surface.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NOT_CLOCKED_IN | Not clocked in | The employee has no active clock-in. | Check with `GET /business-made/timesheets/clock-in/{employeeId}/active`. |\n| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/on-the-clock`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The completed session","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Not clocked in — The employee has no active clock-in.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Not clocked in","path":"/business-made/timesheets/clock-out/{employeeId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Timesheet not found — No timesheet has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Timesheet not found","path":"/business-made/timesheets/clock-out/{employeeId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"requestBody":{"description":"Optional clock-out details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Left early — appointment"}}}}}},"/business-made/timesheets/clock-events/needs-review":{"get":{"operationId":"TimesheetController_clockEventsNeedingReview","summary":"Clock-ins waiting for review","description":"Clock events (bm_clock_event) that did not match the rules — outside the allowed area, no location, no photo. Nobody is turned away at the door, so they land here instead. Newest first; reviewing one takes it off the list.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/clock-events/needs-review (employeeId?: string, from?: string, to?: string, page?: integer, pageSize?: integer) -> `{ data: bm_clock_event[], total, … }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/clock-events/{clockEventId}/review`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","in":"query","required":false,"schema":{"type":"string"},"example":"EMP-4821"},{"name":"from","in":"query","required":false,"description":"Earliest event time (ISO).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Latest event time (ISO).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"page","in":"query","required":false,"schema":{"type":"integer"},"example":1},{"name":"pageSize","in":"query","required":false,"description":"Default 200.","schema":{"type":"integer"},"example":200}],"responses":{"200":{"description":"`{ data: bm_clock_event[], total, … }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"]}},"/business-made/timesheets/clock-events/timesheet/{timesheetId}":{"get":{"operationId":"TimesheetController_clockEventsForTimesheet","summary":"Clock events behind a timesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"timesheetId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_timesheet sk.","example":"TS-4821"}],"responses":{"200":{"description":"`{ data: bm_clock_event[], total }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Every clock in and out recorded against one timesheet, oldest first (up to 500) — where its paid hours came from.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/clock-events/timesheet/{timesheetId} (timesheetId: string) -> `{ data: bm_clock_event[], total }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/timesheets/clock-events/{clockEventId}/review":{"post":{"operationId":"TimesheetController_reviewClockEvent","summary":"Accept a flagged clock-in","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"clockEventId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_clock_event sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"The updated clock event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Marks a clock event reviewed: records who looked (`reviewedBy`, default the caller) and why it was accepted, and clears `needsReview`.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/clock-events/{clockEventId}/review (clockEventId: string, body) -> The updated clock event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reviewedBy":{"type":"string"},"note":{"type":"string"}}},"example":{"note":"Parked across the street — confirmed with the shift lead"}}}}}},"/business-made/timesheets/clock-ins/close-stale":{"post":{"operationId":"TimesheetController_closeStaleClockIns","summary":"Close stale clock-ins","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ closed, skipped, dryRun, olderThanHours, oldestClockIn, entries:[{timesheetId, entryId, employeeId, employeeName, reason, clockInTime, elapsedHours}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Closes clock-ins left open longer than `olderThanHours` (default 24), and ones whose employee no longer exists, **at 0 hours** — nobody is paid for a forgotten clock-out. `employeeIds` limits it to those people. `dryRun: true` returns the same list without writing.\n\n#### Signature\n\n```http\nPOST /business-made/timesheets/clock-ins/close-stale (body) -> `{ closed, skipped, dryRun, olderThanHours, oldestClockIn, entries:[{timesheetId, entryId, employeeId, employeeName, reason, clockInTime, elapsedHours}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"olderThanHours":{"type":"number","default":24},"employeeIds":{"type":"array","items":{"type":"string"}},"dryRun":{"type":"boolean"}}},"example":{"olderThanHours":18,"dryRun":true}}}}}},"/business-made/timesheets/clock-in/{employeeId}/active":{"get":{"operationId":"TimesheetController_getActiveClockIn","summary":"Get an active clock-in","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The active session, or none","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Whether an employee is currently clocked in, and since when. What a clock-in button reads to decide which action to offer.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/clock-in/{employeeId}/active (employeeId: string) -> The active session, or none\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/timesheets/clock-out/{employeeId}`"}},"/business-made/timesheets/reports/summary/{employeeId}":{"get":{"operationId":"TimesheetController_getTimesheetSummary","summary":"Get an employee time summary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"endDate","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The time summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Hours worked by one employee over a period, broken down by project or cost code.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/reports/summary/{employeeId} (employeeId: string) -> The time summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/team-status`"}},"/business-made/timesheets/reports/on-the-clock":{"get":{"operationId":"TimesheetController_getOnTheClock","summary":"List who is on the clock","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Employees currently clocked in","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Everyone currently clocked in. Also where forgotten clock-outs show up — a session running far longer than a shift usually means someone left without clocking out.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/reports/on-the-clock () -> Employees currently clocked in\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/team-status`"}},"/business-made/timesheets/reports/team-status":{"get":{"operationId":"TimesheetController_getTeamTimesheetStatus","summary":"Get team time status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"periodStart","required":true,"in":"query","schema":{"type":"string"}},{"name":"periodEnd","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Team status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Who is working, on leave or absent right now — the shift-supervisor view.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/reports/team-status () -> Team status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/on-the-clock`"}},"/business-made/timesheets/reports/missing":{"get":{"operationId":"TimesheetController_getMissingTimesheets","summary":"List missing timesheets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"start","required":true,"in":"query","schema":{"type":"string"}},{"name":"end","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Missing timesheets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Timesheets"],"description":"Employees who have not submitted a timesheet for a period — the chase list before a payroll run, and the one to clear first.\n\n#### Signature\n\n```http\nGET /business-made/timesheets/reports/missing () -> Missing timesheets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/status/pending`"}},"/business-made/payroll/runs/{id}/parallel-check":{"post":{"operationId":"PayrollController_parallelCheck","summary":"Compare a run against an incumbent provider","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"201":{"description":"The comparison, showing any discrepancies","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/{id}/parallel-check","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Compares a calculated run line by line against figures from your existing payroll provider — the parallel run every migration should do before cutting over.\n\nPost the incumbent's numbers and the response reports where the two disagree. It changes nothing; it is purely a reconciliation.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/{id}/parallel-check (id: string, body) -> The comparison, showing any discrepancies\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only. Run this before the first live payroll on a new setup.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/calculate`","requestBody":{"description":"The incumbent provider's figures for the same period.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["incumbent"],"properties":{"incumbent":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"One entry per employee, as the incumbent calculated them."}}},"example":{"incumbent":[{"employeeId":"EMP-4821","gross":3200,"netPay":2410.55,"federalTax":512}]}}}}}},"/business-made/payroll/runs":{"get":{"operationId":"PayrollController_getPayrollRuns","summary":"List payroll runs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Payroll runs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Payroll runs across the org, with their status and pay period.\n\n#### Signature\n\n```http\nGET /business-made/payroll/runs () -> Payroll runs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/runs/{id}`"},"post":{"operationId":"PayrollController_createPayrollRun","summary":"Create a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Opens a payroll run for a pay period. Creating one calculates nothing and pays nobody — it is a container the rest of the lifecycle acts on.\n\nClear the pre-run blockers first: missing timesheets and employees without a payroll profile will otherwise surface at calculation.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs (body) -> The created run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Check `GET /business-made/payroll/reports/blockers` before creating a run.\n- Runs are posted on `payDate` — a future payDate creates a future-dated run, so use the real pay date, not the example verbatim.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/reports/blockers`\n- `POST /business-made/payroll/runs/{id}/calculate`","requestBody":{"description":"The run to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"payPeriodStart":"2026-01-01","payPeriodEnd":"2026-01-15","payDate":"2026-01-20"}}}}}},"/business-made/payroll/runs/{id}":{"get":{"operationId":"PayrollController_getPayrollRun","summary":"Get a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"200":{"description":"The payroll run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Fetches one run with its totals and current status.\n\n#### Signature\n\n```http\nGET /business-made/payroll/runs/{id} (id: string) -> The payroll run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/calculate`"},"delete":{"operationId":"PayrollController_deletePayrollRun","summary":"Delete a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Deletes a run. For one already processed this destroys the record people were paid from — cancel instead, which keeps it.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/runs/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Destroys the payroll audit trail for that period.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/cancel`"}},"/business-made/payroll/runs/update":{"post":{"operationId":"PayrollController_updatePayrollRun","summary":"Update a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Updates a run's own fields. Recalculate afterwards if anything affecting pay changed — the stored totals are not refreshed automatically.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/update (body) -> The updated run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/calculate`","requestBody":{"description":"The run to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"PR-4821","payDate":"2026-01-21"}}}}}},"/business-made/payroll/runs/{id}/calculate":{"post":{"operationId":"PayrollController_calculatePayrollRun","summary":"Calculate a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"201":{"description":"The calculated run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/{id}/calculate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Works out gross pay, deductions and taxes for everyone in the run, producing the figures for review.\n\nCalculation moves no money and can be repeated — re-run it after correcting a timesheet or a deduction. Employees without a payroll profile cannot be calculated and will show up as blockers.\n\n**State and local withholding follow the employee's signed state certificate.** The elections on `profile.data.stateTax` (written when the certificate is signed) are applied to the work state's table:\n- An exempt claim withholds nothing through `exemptExpiresOn`; from the next day the rest of the certificate applies again (Arizona falls back to its 2.0% default).\n- `extraWithholding` is added each period; `reducedWithholding` comes off; `specifiedWithholding` replaces the computed amount.\n- Tables with `calculationType: \"state_withholding\"` also apply the certificate itself, as each state's published method says: allowances as a wage deduction (NY $1,000, MD $3,200) or a tax credit (CA $168.30), dollar exemption amounts (IN), estimated-deduction allowances (CA), the elected percentage (AZ), and the schedule the filing status or withholding code selects (CT-W4 codes A-F, NJ-W4 rate tables A-E). Shipped for 2026: CA, NY, AZ, MD, IN, CT, NJ, OH (Ohio switches formula for payrolls ending on or after Aug 1 2026).\n- Legacy bracket/flat tables (CA/NY 2025, PA) keep computing exactly as before — only the shared adjustments above apply.\n\n**Local tax** comes from the local lines on the certificate (`stateTax.localities`): NYC and Yonkers residence (IT-2104), Maryland county (MW507, or the .0225 nonresident rate), Indiana county (WH-4) and Ohio school district (IT 4). With no certificate on file, NYC and Yonkers are recognised from the home address city. Each local line is its own tax row carrying `locality` (e.g. `NY:NYC`, `MD:16`, `OH:SD8701`). A Maryland employee with no county on file gets a warning on the stub — only the state portion was withheld.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/{id}/calculate (id: string) -> The calculated run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Safe to repeat. Nothing is paid until `process`.\n- An employee with no state elections gets the same state figure as before; only tables newly shipped for a year change what that year withholds.\n- Stores `payReadiness` on the run and flags held entries in `payrollEntries` (see run preview). `totals.heldCount` counts them.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/parallel-check`\n- `POST /business-made/payroll/runs/{id}/approve`"}},"/business-made/payroll/runs/{id}/approve":{"post":{"operationId":"PayrollController_approvePayrollRun","summary":"Approve a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"201":{"description":"The approved run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Signs off the calculated figures. Approval is the human check between calculation and payment — the last point at which a mistake is cheap to fix.\n\nThe approver is taken from the authenticated caller and recorded on the run.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/{id}/approve (id: string) -> The approved run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Approving still pays nobody. `process` does that.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/process`"}},"/business-made/payroll/runs/{id}/process":{"post":{"operationId":"PayrollController_processPayrollRun","summary":"Process a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"201":{"description":"The processed run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/{id}/process","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Executes the run — generates pay stubs and commits the payments.\n\n**This is the irreversible step.** Everything before it can be recalculated or cancelled; once processed, money is moving and correcting it means an off-cycle adjustment rather than an edit.\n\nConfirm the run is approved and the figures reviewed before calling it.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/{id}/process (id: string) -> The processed run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Irreversible. There is no unprocess.\n- Generate the ACH file separately once processed — see `POST /business-made/payroll/runs/{runId}/ach/generate`.\n- Re-checks pay readiness before paying: a W-4 filed since review releases a hold; a new block holds that pay item. Held entries stay on the run with reasons; the employee is notified.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{runId}/ach/generate`\n- `GET /business-made/payroll/stubs/run/{payrollRunId}`"}},"/business-made/payroll/runs/{id}/cancel":{"post":{"operationId":"PayrollController_cancelPayrollRun","summary":"Cancel a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"201":{"description":"The cancelled run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll run not found — No payroll run has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll run not found","path":"/business-made/payroll/runs/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Cancels a run before it is processed, keeping the record and the reason. The way to abandon a run without destroying evidence that it existed.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/{id}/cancel (id: string, body) -> The cancelled run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAYROLL_RUN_NOT_FOUND | Payroll run not found | No payroll run has that id. | The body carries `code` and the `payrollRunId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/payroll/runs/{id}`","requestBody":{"description":"Why it was cancelled.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Timesheets for the warehouse team were not approved in time"}}},"example":{"reason":"Timesheets not approved in time"}}}}}},"/business-made/payroll/stubs":{"get":{"operationId":"PayrollController_getPayStubs","summary":"List pay stubs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Pay stubs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Pay stubs across the org. These contain pay and deduction detail — restrict access accordingly.\n\n#### Signature\n\n```http\nGET /business-made/payroll/stubs () -> Pay stubs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Highly sensitive personal data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/stubs/employee/{employeeId}`"}},"/business-made/payroll/stubs/{id}":{"get":{"operationId":"PayrollController_getPayStub","summary":"Get a pay stub","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Pay stub id.","example":"STB-4821"}],"responses":{"200":{"description":"The pay stub","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Fetches one pay stub with its earnings, deductions and taxes.\n\n#### Signature\n\n```http\nGET /business-made/payroll/stubs/{id} (id: string) -> The pay stub\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/stubs/{stubId}/pdf`"}},"/business-made/payroll/stubs/employee/{employeeId}":{"get":{"operationId":"PayrollController_getPayStubsByEmployee","summary":"Get an employee's pay stubs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The employee's pay stubs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"One employee's pay stub history — what a self-service payslip screen reads.\n\n#### Signature\n\n```http\nGET /business-made/payroll/stubs/employee/{employeeId} (employeeId: string) -> The employee's pay stubs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scope this tightly — an employee must see only their own.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/stubs/run/{payrollRunId}`"}},"/business-made/payroll/stubs/run/{payrollRunId}":{"get":{"operationId":"PayrollController_getPayStubsByPayrollRun","summary":"Get pay stubs for a run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payrollRunId","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"200":{"description":"Pay stubs for the run","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Every stub produced by one payroll run — the reconciliation view after processing.\n\n#### Signature\n\n```http\nGET /business-made/payroll/stubs/run/{payrollRunId} (payrollRunId: string) -> Pay stubs for the run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{runId}/stubs/pdf/generate`"}},"/business-made/payroll/employer-setup":{"get":{"operationId":"PayrollController_getEmployerTaxSetup","summary":"Get employer tax setup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Employer tax setup","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"The org's employer tax registration — the identifiers filings are made under.\n\n#### Signature\n\n```http\nGET /business-made/payroll/employer-setup () -> Employer tax setup\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/employer-setup/readiness`"},"post":{"operationId":"PayrollController_updateEmployerTaxSetup","summary":"Update employer tax setup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated setup","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Updates the employer tax registration. These identifiers appear on statutory filings, so an error here propagates to every form filed afterwards.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/employer-setup (body) -> The updated setup\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Check readiness after changing anything.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/employer-setup/readiness`","requestBody":{"description":"The setup to store.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ein":"12-3456789","stateRegistrations":[{"state":"CA","id":"123-4567-8"}]}}}}}},"/business-made/payroll/employer-setup/readiness":{"get":{"operationId":"PayrollController_assessEmployerTaxSetup","summary":"Check filing readiness","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"What is missing for valid filings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Reports what is still missing before statutory filings would be valid — an unregistered state, an absent EIN, an incomplete deposit schedule.\n\nRun this before the first payroll of a year, and after adding a state. A filing made without it is the kind of error that is discovered by a tax authority rather than by you.\n\n#### Signature\n\n```http\nGET /business-made/payroll/employer-setup/readiness () -> What is missing for valid filings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employer-setup`"}},"/business-made/payroll/profiles/migrate-tax-elections":{"post":{"operationId":"PayrollController_migrateTaxElections","summary":"Migrate legacy tax elections","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The migration result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"One-off migration that moves legacy flat tax elections onto the structured `data.federalTax` and `data.stateTax` fields.\n\nAn administrative operation for an existing installation — it rewrites tax elections across profiles, so run it once, deliberately, and check the results before the next payroll.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/profiles/migrate-tax-elections (body) -> The migration result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Touches tax elections across every profile. Verify a sample before running payroll.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/profiles`","requestBody":{"description":"Optional migration settings.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/business-made/payroll/profiles":{"get":{"operationId":"PayrollController_getPayrollProfiles","summary":"List payroll profiles","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Payroll profiles","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"The payroll profiles in the org. A profile holds pay rate, tax elections, deductions and bank details — an employee without one cannot be paid.\n\n#### Signature\n\n```http\nGET /business-made/payroll/profiles () -> Payroll profiles\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/profiles/employee/{employeeId}`"},"post":{"operationId":"PayrollController_createPayrollProfile","summary":"Create a payroll profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Creates the payroll profile for an employee — pay rate, tax elections and pay frequency. This is what makes someone payable, and its absence is a pre-run blocker.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/profiles (body) -> The created profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/profiles/employee/{employeeId}/bank-accounts`","requestBody":{"description":"The profile to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","payType":"salary","annualSalary":76800,"payFrequency":"semi-monthly","federalTax":{"filingStatus":"single","allowances":1}}}}}}},"/business-made/payroll/profiles/{id}":{"get":{"operationId":"PayrollController_getPayrollProfile","summary":"Get a payroll profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Profile id.","example":"PRF-4821"}],"responses":{"200":{"description":"The profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Fetches one payroll profile.\n\n#### Signature\n\n```http\nGET /business-made/payroll/profiles/{id} (id: string) -> The profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/profiles/update`"},"delete":{"operationId":"PayrollController_deletePayrollProfile","summary":"Delete a payroll profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Profile id.","example":"PRF-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Deletes a payroll profile. The employee becomes unpayable and will appear as a blocker on the next run — usually you want to terminate the employee instead.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/profiles/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/terminate`"}},"/business-made/payroll/profiles/employee/{employeeId}":{"get":{"operationId":"PayrollController_getPayrollProfileByEmployee","summary":"Get an employee's payroll profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/employee/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"The payroll profile for one employee, looked up by employee rather than profile id.\n\n#### Signature\n\n```http\nGET /business-made/payroll/profiles/employee/{employeeId} (employeeId: string) -> The profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/profiles`"}},"/business-made/payroll/profiles/update":{"post":{"operationId":"PayrollController_updatePayrollProfile","summary":"Update a payroll profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Updates a payroll profile. Changes apply to **future** runs — a run already calculated keeps the figures it was calculated with until recalculated.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/profiles/update (body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Recalculate any open run after a pay change, or it will pay the old rate.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/calculate`","requestBody":{"description":"The profile to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"PRF-4821","annualSalary":82000}}}}}},"/business-made/payroll/profiles/employee/{employeeId}/deductions":{"post":{"operationId":"PayrollController_addDeduction","summary":"Add a deduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/employee/{employeeId}/deductions","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Adds a recurring deduction to an employee's profile — a pension contribution, a garnishment, a benefit premium. It applies from the next calculated run.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/profiles/employee/{employeeId}/deductions (employeeId: string, body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `preTax` changes the taxable base — getting it wrong misstates tax withheld.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/profiles/employee/{employeeId}/deductions/{deductionId}`","requestBody":{"description":"The deduction to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"type":"pension","amount":250,"frequency":"per-period","preTax":true}}}}}},"/business-made/payroll/profiles/employee/{employeeId}/deductions/{deductionId}":{"post":{"operationId":"PayrollController_updateDeduction","summary":"Update a deduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"deductionId","required":true,"in":"path","schema":{"type":"string"},"description":"Deduction id.","example":"DED-4821"}],"responses":{"201":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/employee/{employeeId}/deductions/{deductionId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Updates one of an employee's deductions. Takes effect on the next calculation, not retrospectively.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/profiles/employee/{employeeId}/deductions/{deductionId} (employeeId: string, deductionId: string, body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/payroll/profiles/employee/{employeeId}/deductions/{deductionId}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"amount":300}}}}},"delete":{"operationId":"PayrollController_removeDeduction","summary":"Remove a deduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"deductionId","required":true,"in":"path","schema":{"type":"string"},"description":"Deduction id.","example":"DED-4821"}],"responses":{"200":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/employee/{employeeId}/deductions/{deductionId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Removes a deduction from an employee's profile. Historical stubs keep the deductions they were calculated with.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/profiles/employee/{employeeId}/deductions/{deductionId} (employeeId: string, deductionId: string) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A court-ordered garnishment should usually be stopped through its own end date rather than deleted.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/profiles/employee/{employeeId}/deductions`"}},"/business-made/payroll/profiles/employee/{employeeId}/bank-accounts":{"post":{"operationId":"PayrollController_addBankAccount","summary":"Add a bank account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/employee/{employeeId}/bank-accounts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Adds a bank account for direct deposit.\n\nA new account should be **verified** before it is paid into — an unverified account is where payroll diversion fraud lands. Treat a change of bank details as a security event, not a routine edit.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/profiles/employee/{employeeId}/bank-accounts (employeeId: string, body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Verify before the next run — see the verify endpoint.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId}/verify`","requestBody":{"description":"The account to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"accountType":"checking","routingNumber":"021000021","accountNumber":"000123456789","allocationPercent":100}}}}}},"/business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId}/verify":{"post":{"operationId":"PayrollController_verifyBankAccount","summary":"Verify a bank account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Bank account id.","example":"BNK-4821"}],"responses":{"201":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId}/verify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Marks a bank account as verified, allowing direct deposit to it. This is the control that stops pay going to an account nobody checked.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId}/verify (employeeId: string, accountId: string) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId}`"}},"/business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId}":{"delete":{"operationId":"PayrollController_removeBankAccount","summary":"Remove a bank account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Bank account id.","example":"BNK-4821"}],"responses":{"200":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payroll profile not found — The employee has no payroll profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payroll profile not found","path":"/business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Removes a bank account from a profile. Removing the only account leaves the employee with nowhere to be paid — add the replacement first.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/profiles/employee/{employeeId}/bank-accounts/{accountId} (employeeId: string, accountId: string) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROFILE_NOT_FOUND | Payroll profile not found | The employee has no payroll profile. | Create one with `POST /business-made/payroll/profiles`. An employee without a profile cannot be paid — they appear in the pre-run blockers. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/profiles/employee/{employeeId}/bank-accounts`"}},"/business-made/payroll/reports/summary/{year}":{"get":{"operationId":"PayrollController_getPayrollSummary","summary":"Get an annual payroll summary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":true,"in":"path","schema":{"type":"string"},"description":"Calendar year.","example":"2026"}],"responses":{"200":{"description":"The annual summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Payroll totals for a year — gross, taxes and deductions. The figures year-end filings are reconciled against.\n\n#### Signature\n\n```http\nGET /business-made/payroll/reports/summary/{year} (year: string) -> The annual summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/reports/w2/{employeeId}/{year}`"}},"/business-made/payroll/reports/w2/{employeeId}/{year}":{"get":{"operationId":"PayrollController_getEmployeeW2Data","summary":"Get W-2 data for an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"year","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"}],"responses":{"200":{"description":"W-2 data","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"The W-2 figures for one employee and tax year, as calculated from their processed runs.\n\nThis is the data behind the form. Generating the filed document is a separate step through the payroll configuration controller.\n\n#### Signature\n\n```http\nGET /business-made/payroll/reports/w2/{employeeId}/{year} (employeeId: string, year: string) -> W-2 data\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Reconcile against the annual summary before filing — a discrepancy here becomes a corrected W-2 later.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/reports/summary/{year}`"}},"/business-made/payroll/reports/upcoming-run":{"get":{"operationId":"PayrollController_getUpcomingRun","summary":"Get the upcoming payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"start","required":true,"in":"query","schema":{"type":"string"},"description":"Pay period start (YYYY-MM-DD).","example":"2026-09-01"},{"name":"end","required":true,"in":"query","schema":{"type":"string"},"description":"Pay period end.","example":"2026-09-15"},{"name":"payDate","required":true,"in":"query","schema":{"type":"string"},"example":"2026-09-19"}],"responses":{"200":{"description":"The upcoming run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"The next scheduled run and its pay period — what the blocker list should be cleared against.\n\n#### Signature\n\n```http\nGET /business-made/payroll/reports/upcoming-run (start?: string, end?: string, payDate?: string) -> The upcoming run\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/reports/blockers`"}},"/business-made/payroll/reports/blockers":{"get":{"operationId":"PayrollController_getRunBlockers","summary":"Get pre-run blockers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"start","required":true,"in":"query","schema":{"type":"string"},"description":"Pay period start (YYYY-MM-DD).","example":"2026-09-01"},{"name":"end","required":true,"in":"query","schema":{"type":"string"},"description":"Pay period end.","example":"2026-09-15"}],"responses":{"200":{"description":"Outstanding blockers","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll"],"description":"Everything standing between you and a clean payroll run: missing timesheets, submissions still awaiting approval, and employees with no payroll profile.\n\n**Run this before every payroll.** Each blocker is something that would otherwise be discovered during calculation, or worse, produce a run that pays someone the wrong amount.\n\n#### Signature\n\n```http\nGET /business-made/payroll/reports/blockers (start?: string, end?: string) -> Outstanding blockers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/missing`\n- `POST /business-made/payroll/runs`"}},"/business-made/schedules":{"get":{"operationId":"ScheduleController_getSchedules","summary":"List schedules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Schedules","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Work schedules across the org.\n\n#### Signature\n\n```http\nGET /business-made/schedules () -> Schedules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/schedules/shifts/today`"},"post":{"operationId":"ScheduleController_createSchedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Create a schedule","description":"Creates a schedule as a draft. Staff do not see it until it is published, so a rota can be built and reworked freely.\n\n#### Signature\n\n```http\nPOST /business-made/schedules (body) -> The created schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts`","requestBody":{"description":"The schedule to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Week 40","startDate":"2026-09-28","endDate":"2026-10-04","location":"london"}}}}}},"/business-made/schedules/settings":{"get":{"operationId":"ScheduleController_getSchedulingSettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Location sk or slug; `all` = rollup.","example":"harbor-grill"}],"responses":{"200":{"description":"`{ shiftTemplates, patternType, cycleDays, cycleStart, planningHorizonDays, locationId, scope: \"org\" | \"location\", rollup }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Get scheduling settings","description":"The shift definitions (`shiftTemplates`: times, headcount, roles, break, days, computed `paidHours`) and planning rules (`patternType` weekly or cycle, `cycleDays`, `cycleStart`, `planningHorizonDays`). **Scheduling is per location**: with a location its own settings are laid over the org's and `scope` says which applied; `all`/nothing is the rollup.\n\n#### Signature\n\n```http\nGET /business-made/schedules/settings (businessLocationId?: string) -> `{ shiftTemplates, patternType, cycleDays, cycleStart, planningHorizonDays, locationId, scope: \"org\" \\| \"location\", rollup }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"post":{"operationId":"ScheduleController_saveSchedulingSettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"harbor-grill"}],"responses":{"201":{"description":"The saved settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Save scheduling settings","description":"Saves shift definitions and planning rules — for a location when one is given (query or body `businessLocationId`), otherwise org-wide. Returns the settings as GET shows them.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/settings (businessLocationId?: string, body) -> The saved settings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"patternType":"weekly","planningHorizonDays":14,"shiftTemplates":[{"name":"Lunch","startTime":"11:00","endTime":"15:00","headcount":3,"breakMinutes":15,"days":["mon","tue","wed","thu","fri"]}]}}}}}},"/business-made/schedules/assignable":{"get":{"operationId":"ScheduleController_getAssignable","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"date","required":true,"in":"query","schema":{"type":"string"}},{"name":"startTime","required":true,"in":"query","schema":{"type":"string"}},{"name":"endTime","required":true,"in":"query","schema":{"type":"string"}},{"name":"excludeShiftId","required":true,"in":"query","schema":{"type":"string"}},{"name":"businessLocationId","required":true,"in":"query","schema":{"type":"string"}},{"name":"station","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness gates"],"summary":"Who can work a slot","description":"#### Signature\n\n```http\nGET /business-made/schedules/assignable ()\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Each person in `available[]` / `onLeave[]` carries `readiness: { effect: none|warn|override|block, badge: blocked|overdue|due_soon|null, reasons[], overrideActive }` for this slot. Pass `station` to judge station rules. Nobody is removed; people who qualify sort first. `readinessBlocked` counts the blocked.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/schedules/month":{"get":{"operationId":"ScheduleController_getMonth","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"month","required":true,"in":"query","schema":{"type":"string"},"description":"YYYY-MM.","example":"2026-10"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"harbor-grill"},{"name":"q","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by person or shift text.","example":"Ana"}],"responses":{"200":{"description":"`{ month, gridStart, days:[{date, inMonth, shifts, people, open, needed, short, hours, cost, published, state}], totals:{shifts, open, short, hours, cost, daysScheduled, daysInMonth} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"month must be YYYY-MM — `month` is missing or malformed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"month must be YYYY-MM","path":"/business-made/schedules/month","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"A month of coverage","description":"For every day on the calendar grid (whole weeks around the month): shifts, people, open shifts, how many are needed and how many short, hours, cost, whether it is published, and one `state` (`empty`, `short`, `open`, `covered`). Totals cover the month's own days; only days with a rota count as short.\n\n#### Signature\n\n```http\nGET /business-made/schedules/month (month?: string, businessLocationId?: string, q?: string) -> `{ month, gridStart, days:[{date, inMonth, shifts, people, open, needed, short, hours, cost, published, state}], totals:{shifts, open, short, hours, cost, daysScheduled, daysInMonth} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BAD_MONTH | month must be YYYY-MM | `month` is missing or malformed. | Send YYYY-MM. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/schedules/running":{"get":{"operationId":"ScheduleController_getRunning","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"date","required":false,"in":"query","schema":{"type":"string"},"example":"2026-10-07"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"harbor-grill"}],"responses":{"200":{"description":"`{ date, isToday, asOf, rows:[shift + {status, minutesLate, clockInTime, clockOutTime}], totals:{shifts, on, late, noShow, done, upcoming, open} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"The day as it is going","description":"Every shift on the date (default today) joined to its clock-ins, with a status decided on the server — upcoming, on, late (with `minutesLate`), no-show, done or open.\n\n#### Signature\n\n```http\nGET /business-made/schedules/running (date?: string, businessLocationId?: string) -> `{ date, isToday, asOf, rows:[shift + {status, minutesLate, clockInTime, clockOutTime}], totals:{shifts, on, late, noShow, done, upcoming, open} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/schedules/availability/{employeeId}":{"get":{"operationId":"ScheduleController_getAvailability","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The availability record, or null","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Get a person's availability","description":"What the person said they can work (bm_availability): weekly pattern, exceptions, maximum hours a week, notes. Returns null when nothing is recorded.\n\n#### Signature\n\n```http\nGET /business-made/schedules/availability/{employeeId} (employeeId: string) -> The availability record, or null\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"ScheduleController_setAvailability","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The saved availability record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Set a person's availability","description":"Creates or replaces the person's availability as their manager. Availability is a preference: someone outside it can still be scheduled, flagged.\n\n#### Signature\n\n```http\nPUT /business-made/schedules/availability/{employeeId} (employeeId: string, body) -> The saved availability record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"weekly":{"type":"array","items":{"type":"object","additionalProperties":true}},"exceptions":{"type":"array","items":{"type":"object","additionalProperties":true}},"maxHoursPerWeek":{"type":"number","nullable":true},"notes":{"type":"string"}}},"example":{"weekly":[{"day":"mon","from":"09:00","to":"17:00"}],"exceptions":[{"date":"2026-10-12","available":false}],"maxHoursPerWeek":30}}}}}},"/business-made/schedules/week":{"get":{"operationId":"ScheduleController_getWeek","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"weekStart","required":true,"in":"query","schema":{"type":"string"}},{"name":"businessLocationId","required":true,"in":"query","schema":{"type":"string"}},{"name":"lengthDays","required":true,"in":"query","schema":{"type":"string"}},{"name":"q","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness gates"],"summary":"The week rota","description":"#### Signature\n\n```http\nGET /business-made/schedules/week ()\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Readiness badges: every assigned shift carries `readiness` (null when no rule applies) = `{ employeeId, effect, badge: blocked|overdue|due_soon, blocked, reasons[], overrideActive }` and `readinessOverride` when one was recorded. Rows / `byPerson` carry `readinessBadge` (worst) and `readinessBlocked`; `totals.readiness` = `{ blocked, overdue, dueSoon }`.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/schedules/{id}":{"get":{"operationId":"ScheduleController_getSchedule","summary":"Get a schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/schedules/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Fetches one schedule with its shifts.\n\n#### Signature\n\n```http\nGET /business-made/schedules/{id} (id: string) -> The schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/publish`"},"delete":{"operationId":"ScheduleController_deleteSchedule","summary":"Delete a schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot delete a published schedule — The schedule has been published.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot delete a published schedule","path":"/business-made/schedules/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Deletes a draft schedule. **A published schedule cannot be deleted** — staff have already planned around it. Archive it instead.\n\n#### Signature\n\n```http\nDELETE /business-made/schedules/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n| `400` | CANNOT_DELETE_PUBLISHED | Cannot delete a published schedule | The schedule has been published. | Archive it with `POST /business-made/schedules/{id}/archive`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/archive`"}},"/business-made/schedules/generate":{"post":{"operationId":"ScheduleController_generatePeriod","summary":"Build a period from the shift definitions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ scheduleId, from, lengthDays, created, filled, leftOpen }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick a location to fill — each location has its own rota. — No location (or `all`) was given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick a location to fill — each location has its own rota.","path":"/business-made/schedules/generate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"For every shift definition, on every day it runs, creates the shifts it asks for. Existing shifts are counted first, so running it twice tops a period up rather than duplicating it. With `assign: true` people are placed too — ready, available, not on leave, the least-loaded first. Needs one location: each location has its own rota.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/generate (body) -> `{ scheduleId, from, lengthDays, created, filled, leftOpen }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | LOCATION_REQUIRED | Pick a location to fill — each location has its own rota. | No location (or `all`) was given. | Send `businessLocationId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from"],"properties":{"from":{"type":"string","format":"date"},"lengthDays":{"type":"integer"},"businessLocationId":{"type":"string"},"assign":{"type":"boolean"}}},"example":{"from":"2026-10-05","lengthDays":7,"businessLocationId":"harbor-grill","assign":true}}}}}},"/business-made/schedules/person/clear":{"post":{"operationId":"ScheduleController_clearPersonShifts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ employeeId, from, cleared, stillNeedCover }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Take one person off a period","description":"Removes the person from every shift in the period. The shifts are **not deleted** — the work still needs doing, so each goes back to needing cover. Nobody is emailed; ask for cover explicitly.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/person/clear (body) -> `{ employeeId, from, cleared, stillNeedCover }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/shift/cover`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["employeeId","from"],"properties":{"employeeId":{"type":"string"},"from":{"type":"string","format":"date"},"lengthDays":{"type":"integer"},"businessLocationId":{"type":"string"}}},"example":{"employeeId":"EMP-4821","from":"2026-10-05","lengthDays":7}}}}}},"/business-made/schedules/shift/clear":{"post":{"operationId":"ScheduleController_clearShiftAssignments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ definitionId, date, cleared }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Clear everyone off a shift","description":"Unassigns everyone from one shift definition — on a single `date`, or across the whole period. The shifts remain, open.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/shift/clear (body) -> `{ definitionId, date, cleared }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["definitionId","from"],"properties":{"definitionId":{"type":"string","description":"Shift template id."},"from":{"type":"string","format":"date"},"lengthDays":{"type":"integer"},"date":{"type":"string","format":"date"},"businessLocationId":{"type":"string"}}},"example":{"definitionId":"st-0","from":"2026-10-05","date":"2026-10-07"}}}}}},"/business-made/schedules/shift/cover":{"post":{"operationId":"ScheduleController_requestCover","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ notified, shifts }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Ask for cover on open shifts","description":"Emails the people free to take the open shifts in the period (narrowed by `definitionId` and/or `date`). Batched: each person gets **at most one** email listing every shift they could pick up.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/shift/cover (body) -> `{ notified, shifts }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from"],"properties":{"from":{"type":"string","format":"date"},"lengthDays":{"type":"integer"},"definitionId":{"type":"string"},"date":{"type":"string","format":"date"},"businessLocationId":{"type":"string"}}},"example":{"from":"2026-10-05","lengthDays":7,"businessLocationId":"harbor-grill"}}}}}},"/business-made/schedules/copy":{"post":{"operationId":"ScheduleController_copyPeriod","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ copied, from, to, lengthDays }` — or `{ copied: 0, reason: \"nothing to copy\" }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Copy one period onto another","description":"Copies every shift in the period starting `from` onto the period starting `to`, in one call.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/copy (body) -> `{ copied, from, to, lengthDays }` — or `{ copied: 0, reason: \"nothing to copy\" }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"lengthDays":{"type":"integer"},"businessLocationId":{"type":"string"}}},"example":{"from":"2026-10-05","to":"2026-10-12","lengthDays":7,"businessLocationId":"harbor-grill"}}}}}},"/business-made/schedules/week/publish":{"post":{"operationId":"ScheduleController_publishWeek","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":""},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/business-made/schedules/week/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/business-made/schedules/week/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"<name> can't be scheduled: <requirement titles>. — A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"scheduler","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness gates"],"summary":"Publish a period","description":"#### Signature\n\n```http\nPOST /business-made/schedules/week/publish (body)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scheduler gate over every assigned shift in the period. Any block without an active override stops the whole publish with 409 `readiness_block`; `blocking[]` lists each shift `{ shiftId, scheduleId, date, startTime, endTime, station, employeeId, employeeName, issues[] }`. Resend with `override` to record one override per blocked shift. The result carries `readiness: { overridden[], warnings[] }`.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | READINESS_BLOCK | <name> can't be scheduled: <requirement titles>. | A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}}}},"/business-made/schedules/update":{"post":{"operationId":"ScheduleController_updateSchedule","summary":"Update a schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Updates a schedule. Changing a published one changes what staff already saw — announce it rather than relying on them noticing. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/update (body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/publish`","requestBody":{"description":"The schedule to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"SCH-4821","data":{"name":"Week 40 (revised)"}}}}}}},"/business-made/schedules/{id}/publish":{"post":{"operationId":"ScheduleController_publishSchedule","summary":"Publish a schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The published schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Schedule is already published — The schedule has already been published.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Schedule is already published","path":"/business-made/schedules/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/business-made/schedules/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"<name> can't be scheduled: <requirement titles>. — A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"scheduler","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Makes a schedule visible to staff — the point the rota becomes real and people plan around it. Publishing twice is refused rather than silently re-notifying everyone.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/publish (id: string, body) -> The published schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scheduler gate over the record’s assigned shifts — same 409 shape and `override` as `POST /business-made/schedules/week/publish`.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n| `400` | ALREADY_PUBLISHED | Schedule is already published | The schedule has already been published. | Not idempotent — read the status before publishing. |\n| `409` | READINESS_BLOCK | <name> can't be scheduled: <requirement titles>. | A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/archive`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}}}},"/business-made/schedules/{id}/archive":{"post":{"operationId":"ScheduleController_archiveSchedule","summary":"Archive a schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The archived schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Retires a schedule while keeping it — how past rotas are preserved, and the way to retire a published one.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/archive (id: string) -> The archived schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/schedules/{id}`"}},"/business-made/schedules/{id}/duplicate":{"post":{"operationId":"ScheduleController_duplicateSchedule","summary":"Duplicate a schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The new draft schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/duplicate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Copies a schedule into a new draft — the fast path for a recurring rota. The copy is unpublished.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/duplicate (id: string, body) -> The new draft schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/publish`","requestBody":{"description":"Optional overrides for the copy.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Week 41","startDate":"2026-10-05"}}}}}},"/business-made/schedules/{id}/shifts":{"post":{"operationId":"ScheduleController_addShift","summary":"Add a shift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/business-made/schedules/{id}/shifts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/business-made/schedules/{id}/shifts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Shift conflicts with existing shift — The employee already has a shift overlapping that time.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Shift conflicts with existing shift","path":"/business-made/schedules/{id}/shifts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Adds a shift to a schedule. **Overlapping shifts for the same employee are refused** with a `409` naming the conflicting shift, so nobody is rostered in two places at once.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts (id: string, body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scheduler gate: placing someone a rule blocks from this shift’s station/time is refused with 409 `readiness_block` unless an override is active or the body carries `override`. An override is stamped on the shift as `readinessOverride`. A warn returns the shift with `readiness: { effect, warnings, reasons }`.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n| `409` | SHIFT_CONFLICT | Shift conflicts with existing shift | The employee already has a shift overlapping that time. | The body identifies the conflicting shift. Move one of them. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts/{shiftId}`","requestBody":{"description":"The shift to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}},"example":{"employeeId":"EMP-4821","startTime":"2026-09-28T09:00:00.000Z","endTime":"2026-09-28T17:00:00.000Z","role":"barista"}}}}}},"/business-made/schedules/{id}/shifts/{shiftId}":{"post":{"operationId":"ScheduleController_updateShift","summary":"Update a shift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/business-made/schedules/{id}/shifts/{shiftId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/business-made/schedules/{id}/shifts/{shiftId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Shift conflicts with existing shift — The change would overlap another shift for that employee.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Shift conflicts with existing shift","path":"/business-made/schedules/{id}/shifts/{shiftId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Updates a shift. Overlap checking applies here too — a change that would double-book someone is refused.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId} (id: string, shiftId: string, body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scheduler gate: checked only when the change moves who works, the date/time, or the station (a note edit never trips it). Same 409 / `override` / `readiness` shape as adding a shift.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n| `409` | SHIFT_CONFLICT | Shift conflicts with existing shift | The change would overlap another shift for that employee. | The body identifies the conflicting shift. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/schedules/{id}/shifts/{shiftId}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}},"example":{"endTime":"2026-09-28T18:00:00.000Z"}}}}},"delete":{"operationId":"ScheduleController_removeShift","summary":"Remove a shift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"200":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot remove a shift that has started — The shift has already begun.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot remove a shift that has started","path":"/business-made/schedules/{id}/shifts/{shiftId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Removes a shift from a schedule. **A shift that has started cannot be removed** — someone is working it, and deleting it would erase hours that are owed.\n\n#### Signature\n\n```http\nDELETE /business-made/schedules/{id}/shifts/{shiftId} (id: string, shiftId: string) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n| `400` | SHIFT_IN_PROGRESS | Cannot remove a shift that has started | The shift has already begun. | Let it finish and correct the timesheet instead. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts`"}},"/business-made/schedules/{id}/shifts/{shiftId}/swap/request":{"post":{"operationId":"ScheduleController_requestShiftSwap","summary":"Request a shift swap","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The swap request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}/swap/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Asks to hand a shift to a colleague. Nothing changes until a manager approves — the rota stays authoritative in the meantime.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId}/swap/request (id: string, shiftId: string, body) -> The swap request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts/{shiftId}/swap/approve`","requestBody":{"description":"The swap request.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"toEmployeeId":"EMP-4822","reason":"Medical appointment"}}}}}},"/business-made/schedules/{id}/shifts/{shiftId}/swap/approve":{"post":{"operationId":"ScheduleController_approveShiftSwap","summary":"Approve a shift swap","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/business-made/schedules/{id}/shifts/{shiftId}/swap/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/business-made/schedules/{id}/shifts/{shiftId}/swap/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}/swap/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"<name> can't take over this shift: <requirement titles>. — A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"scheduler","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Approves a swap and reassigns the shift. Overlap rules still apply to the receiving employee.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId}/swap/approve (id: string, shiftId: string, body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scheduler gate for the person taking the shift over; same 409 / `override` shape.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n| `409` | READINESS_BLOCK | <name> can't take over this shift: <requirement titles>. | A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts/{shiftId}/swap/reject`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}}}},"/business-made/schedules/{id}/shifts/{shiftId}/swap/reject":{"post":{"operationId":"ScheduleController_rejectShiftSwap","summary":"Reject a shift swap","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}/swap/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Declines a swap request. The shift stays with whoever was originally rostered.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId}/swap/reject (id: string, shiftId: string, body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts/{shiftId}/swap/approve`","requestBody":{"description":"Why it was rejected.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Receiving employee would exceed weekly hours"}}}}}},"/business-made/schedules/{id}/shifts/{shiftId}/clock-in":{"post":{"operationId":"ScheduleController_clockIn","summary":"Clock in to a shift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated shift","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/business-made/schedules/{id}/shifts/{shiftId}/clock-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/business-made/schedules/{id}/shifts/{shiftId}/clock-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}/clock-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't clock in: <requirement titles>. — A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"clockIn","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Records an employee starting their rostered shift, tying actual hours to the planned ones so variance is visible.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId}/clock-in (id: string, shiftId: string, body) -> The updated shift\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- ClockIn gate for the shift’s station: 423 `readiness_block` on a block; a manager resends with `override`. A warn clocks in and returns `readiness`.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n| `423` | READINESS_BLOCK | <name> can't clock in: <requirement titles>. | A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts/{shiftId}/clock-out`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}}}},"/business-made/schedules/{id}/shifts/{shiftId}/clock-out":{"post":{"operationId":"ScheduleController_clockOut","summary":"Clock out of a shift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated shift","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}/clock-out","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Records an employee finishing their shift. The difference between rostered and actual hours is what the labour report measures.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId}/clock-out (id: string, shiftId: string) -> The updated shift\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/schedules/reports/labor-summary`"}},"/business-made/schedules/{id}/shifts/{shiftId}/break/start":{"post":{"operationId":"ScheduleController_startBreak","summary":"Start a break","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated shift","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}/break/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Records the start of a break within a shift. Where breaks are unpaid, this is what keeps paid hours correct.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId}/break/start (id: string, shiftId: string) -> The updated shift\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts/{shiftId}/break/{breakId}/end`"}},"/business-made/schedules/{id}/shifts/{shiftId}/break/{breakId}/end":{"post":{"operationId":"ScheduleController_endBreak","summary":"End a break","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"},{"name":"breakId","required":true,"in":"path","schema":{"type":"string"},"description":"Break id.","example":"BRK-4821"}],"responses":{"201":{"description":"The updated shift","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Schedule not found — No schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Schedule not found","path":"/business-made/schedules/{id}/shifts/{shiftId}/break/{breakId}/end","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Records the end of a break. An unended break can leave the shift under-counting paid hours.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{id}/shifts/{shiftId}/break/{breakId}/end (id: string, shiftId: string, breakId: string) -> The updated shift\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SCHEDULE_NOT_FOUND | Schedule not found | No schedule has that id. | The body carries `code` and the `scheduleId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{id}/shifts/{shiftId}/break/start`"}},"/business-made/schedules/shifts/employee/{employeeId}":{"get":{"operationId":"ScheduleController_getShiftsByEmployee","summary":"Get an employee's shifts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"endDate","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Shifts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Every shift rostered to one employee.\n\n#### Signature\n\n```http\nGET /business-made/schedules/shifts/employee/{employeeId} (employeeId: string) -> Shifts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/schedules/shifts/employee/{employeeId}/upcoming`"}},"/business-made/schedules/shifts/employee/{employeeId}/upcoming":{"get":{"operationId":"ScheduleController_getUpcomingShifts","summary":"Get an employee's upcoming shifts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"days","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Upcoming shifts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"The shifts an employee has coming up — their \"when am I next in\" view.\n\n#### Signature\n\n```http\nGET /business-made/schedules/shifts/employee/{employeeId}/upcoming (employeeId: string) -> Upcoming shifts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/schedules/shifts/today`"}},"/business-made/schedules/shifts/today":{"get":{"operationId":"ScheduleController_getTodaysShifts","summary":"Get today's shifts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"locationId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Today's shifts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Everyone rostered today — the shift-supervisor view of who should be in.\n\n#### Signature\n\n```http\nGET /business-made/schedules/shifts/today () -> Today's shifts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/on-the-clock`"}},"/business-made/schedules/reports/labor-summary":{"get":{"operationId":"ScheduleController_getLaborSummary","summary":"Get the labour summary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"periodStart","required":true,"in":"query","schema":{"type":"string"}},{"name":"periodEnd","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Labour summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"description":"Rostered against actual hours and their cost — where overtime and understaffing show up before they reach payroll.\n\n#### Signature\n\n```http\nGET /business-made/schedules/reports/labor-summary () -> Labour summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/timesheets/reports/summary/{employeeId}`"}},"/business-made/schedules/{scheduleId}/shifts/{shiftId}/claim":{"post":{"operationId":"ScheduleController_claimShift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"scheduleId","required":true,"in":"path","schema":{"type":"string"}},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"<name> can't take this shift: <requirement titles>. — A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"scheduler","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness gates"],"summary":"Claim an open shift","description":"#### Signature\n\n```http\nPOST /business-made/schedules/{scheduleId}/shifts/{shiftId}/claim ()\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scheduler gate: a person can’t take an open shift a rule blocks them from (409 `readiness_block`). They can’t override their own block; a manager assigns them with an override instead.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | READINESS_BLOCK | <name> can't take this shift: <requirement titles>. | A requirement rule whose `enforcement.scheduler` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/schedules/{scheduleId}/shifts/{shiftId}/offer":{"post":{"operationId":"ScheduleController_offerShift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"scheduleId","required":true,"in":"path","schema":{"type":"string"},"example":"SCH-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Shift not found — No shift on that schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shift not found","path":"/business-made/schedules/{scheduleId}/shifts/{shiftId}/offer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Offer a shift to its person","description":"Puts the shift to the person on it (`status: offered`) and emails them. They accept or decline; nothing is imposed.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{scheduleId}/shifts/{shiftId}/offer (scheduleId: string, shiftId: string) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIFT_NOT_FOUND | Shift not found | No shift on that schedule has that id. | The body carries `code` and the `shiftId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/schedules/{scheduleId}/shifts/{shiftId}/respond`"}},"/business-made/schedules/{scheduleId}/shifts/{shiftId}/respond":{"post":{"operationId":"ScheduleController_respondToShift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"scheduleId","required":true,"in":"path","schema":{"type":"string"},"example":"SCH-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Shift not found — No shift on that schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shift not found","path":"/business-made/schedules/{scheduleId}/shifts/{shiftId}/respond","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Answer a shift offer","description":"`accept: true` marks it accepted. A decline hands the shift back (`status: open`, person removed, `declineReason` kept), tells the managers and asks the free pool for cover.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{scheduleId}/shifts/{shiftId}/respond (scheduleId: string, shiftId: string, body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIFT_NOT_FOUND | Shift not found | No shift on that schedule has that id. | The body carries `code` and the `shiftId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accept"],"properties":{"accept":{"type":"boolean"},"reason":{"type":"string"}}},"example":{"accept":false,"reason":"Exam that morning"}}}}}},"/business-made/schedules/{scheduleId}/shifts/{shiftId}/no-show":{"post":{"operationId":"ScheduleController_markNoShow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"scheduleId","required":true,"in":"path","schema":{"type":"string"},"example":"SCH-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"description":"Shift id.","example":"SHF-4821"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Shift not found — No shift on that schedule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shift not found","path":"/business-made/schedules/{scheduleId}/shifts/{shiftId}/no-show","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Schedules"],"summary":"Mark a no-show","description":"The person did not turn up: the shift goes back out (`status: open`, `noShowOf` records who), the person and the managers are told, and the free pool is asked for cover.\n\n#### Signature\n\n```http\nPOST /business-made/schedules/{scheduleId}/shifts/{shiftId}/no-show (scheduleId: string, shiftId: string, body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIFT_NOT_FOUND | Shift not found | No shift on that schedule has that id. | The body carries `code` and the `shiftId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"No call, no show"}}}}}},"/business-made/work-orders":{"get":{"operationId":"WorkOrderController_getWorkOrders","summary":"List work orders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"page","in":"query","required":false,"schema":{"type":"integer"},"example":1},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"},"example":100}],"responses":{"200":{"description":"Work orders","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Work orders across the org.\n\n#### Signature\n\n```http\nGET /business-made/work-orders (page?: integer, pageSize?: integer) -> Work orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/work-orders/active`"},"post":{"operationId":"WorkOrderController_createWorkOrder","summary":"Create a work order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Raises a work order for a customer or asset.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders (body) -> The created work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/assign`","requestBody":{"description":"The work order to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"customerId":"cus_4821","description":"Annual boiler service","scheduledDate":"2026-10-08"}}}}}},"/business-made/work-orders/active":{"get":{"operationId":"WorkOrderController_getActiveWorkOrders","summary":"List active work orders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active work orders","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Work currently in progress — the dispatch board.\n\n#### Signature\n\n```http\nGET /business-made/work-orders/active () -> Active work orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/assign`"}},"/business-made/work-orders/{id}":{"get":{"operationId":"WorkOrderController_getWorkOrder","summary":"Get a work order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Fetches one work order with its items, time entries and materials.\n\n#### Signature\n\n```http\nGET /business-made/work-orders/{id} (id: string) -> The work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/status`"},"delete":{"operationId":"WorkOrderController_deleteWorkOrder","summary":"Delete a work order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Deletes a work order and the time and materials recorded against it — which is also the billing evidence.\n\n#### Signature\n\n```http\nDELETE /business-made/work-orders/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Destroys the record of work done and materials used.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/status`"}},"/business-made/work-orders/update":{"post":{"operationId":"WorkOrderController_updateWorkOrder","summary":"Update a work order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Updates a work order. Status and assignment have dedicated endpoints.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders/update (body) -> The updated work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/status`","requestBody":{"description":"The work order to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"WO-4821","scheduledDate":"2026-10-10"}}}}}},"/business-made/work-orders/{id}/status":{"post":{"operationId":"WorkOrderController_updateWorkOrderStatus","summary":"Change a work order status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Work order not found — No work order has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Work order not found","path":"/business-made/work-orders/{id}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Moves a work order through its lifecycle — scheduled, in progress, complete.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders/{id}/status (id: string, body) -> The updated work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Work order not found | No work order has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/assign`","requestBody":{"description":"The new status.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","example":"in-progress"}}},"example":{"status":"in-progress"}}}}}},"/business-made/work-orders/{id}/assign":{"post":{"operationId":"WorkOrderController_assignWorkOrder","summary":"Assign a work order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Work order not found — No work order has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Work order not found","path":"/business-made/work-orders/{id}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Assigns the work to one or more people. Replaces the existing assignment rather than adding to it — send the full list.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders/{id}/assign (id: string, body) -> The updated work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `assignedTo` replaces the current assignment.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Work order not found | No work order has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/work-orders/active`","requestBody":{"description":"Who to assign it to.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignedTo"],"properties":{"assignedTo":{"type":"array","items":{"type":"string"},"example":["EMP-4821"]}}},"example":{"assignedTo":["EMP-4821","EMP-4822"]}}}}}},"/business-made/work-orders/{id}/items":{"post":{"operationId":"WorkOrderController_addWorkOrderItem","summary":"Add a work order item","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Work order not found — No work order has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Work order not found","path":"/business-made/work-orders/{id}/items","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Adds a line of work to the order.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders/{id}/items (id: string, body) -> The updated work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Work order not found | No work order has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/items/{itemId}`","requestBody":{"description":"The item to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"description":"Replace pressure valve","quantity":1,"rate":85}}}}}},"/business-made/work-orders/{id}/items/{itemId}":{"post":{"operationId":"WorkOrderController_updateWorkOrderItem","summary":"Update a work order item","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Item id.","example":"WOI-4821"}],"responses":{"201":{"description":"The updated work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Work order not found — No work order has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Work order not found","path":"/business-made/work-orders/{id}/items/{itemId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Updates one line on a work order.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders/{id}/items/{itemId} (id: string, itemId: string, body) -> The updated work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Work order not found | No work order has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/items`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"quantity":2}}}}}},"/business-made/work-orders/{id}/time-entries":{"post":{"operationId":"WorkOrderController_addTimeEntry","summary":"Add a time entry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Work order not found — No work order has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Work order not found","path":"/business-made/work-orders/{id}/time-entries","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Records labour time against a work order. This is what makes the job billable and what the margin on it is measured from.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders/{id}/time-entries (id: string, body) -> The updated work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Work order not found | No work order has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/materials`","requestBody":{"description":"The time entry.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","hours":2.5,"date":"2026-10-08","rate":65}}}}}},"/business-made/work-orders/{id}/materials":{"post":{"operationId":"WorkOrderController_addMaterial","summary":"Add materials","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated work order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Work order not found — No work order has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Work order not found","path":"/business-made/work-orders/{id}/materials","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Records materials consumed on a work order — the cost side of the job.\n\n#### Signature\n\n```http\nPOST /business-made/work-orders/{id}/materials (id: string, body) -> The updated work order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Work order not found | No work order has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/work-orders/{id}/time-entries`","requestBody":{"description":"The materials used.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sku":"PRT-VALVE-22","quantity":1,"unitCost":42.5}}}}}},"/business-made/work-orders/customer/{customerId}":{"get":{"operationId":"WorkOrderController_getWorkOrdersByCustomer","summary":"Get a customer's work orders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"customerId","required":true,"in":"path","schema":{"type":"string"},"description":"Customer id.","example":"cus_4821"}],"responses":{"200":{"description":"Work orders","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Work orders"],"description":"Work orders raised for one customer — their service history.\n\n#### Signature\n\n```http\nGET /business-made/work-orders/customer/{customerId} (customerId: string) -> Work orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/work-orders`"}},"/business-made/recruitment/board":{"get":{"operationId":"RecruitmentController_board","summary":"Hiring board","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","in":"query","required":false,"description":"Job status to show, or `active` (open, on hold, draft).","schema":{"type":"string"},"example":"active"},{"name":"jobPostingId","in":"query","required":false,"description":"Only this job's applicants in the pipeline.","schema":{"type":"string"},"example":"66f0c3a1e4b0a1b2c3d4e5f6"},{"name":"closed","in":"query","required":false,"description":"`true` adds the rejected and withdrawn stages.","schema":{"type":"string"},"example":"false"}],"responses":{"200":{"description":"`{ totals:{openJobs, onHold, filled, inPipeline, hired, notMovingForward}, statusCounts, jobs:[{id, title, department, location, employmentType, pay, status, statusLabel, openings, hired, applicants, inPipeline, byStage, postedAt, closingDate}], stages:[{stage, label, count}], applicants, jobOptions }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Jobs with their pipeline counts (applicants, in pipeline, hired, by stage), the pipeline itself (stages `new` → `screening` → `interview` → `assessment` → `offer` → `hired`, newest applicants first), totals and the jobs open for new applicants. Rejected and withdrawn applicants are left out unless `closed=true`.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/board (status?: string, jobPostingId?: string, closed?: string) -> `{ totals:{openJobs, onHold, filled, inPipeline, hired, notMovingForward}, statusCounts, jobs:[{id, title, department, location, employmentType, pay, status, statusLabel, openings, hired, applicants, inPipeline, byStage, postedAt, closingDate}], stages:[{stage, label, count}], applicants, jobOptions }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/board/jobs/{id}`"}},"/business-made/recruitment/board/jobs":{"post":{"operationId":"RecruitmentController_saveJob","summary":"Create or edit a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The job detail (as GET /business-made/recruitment/board/jobs/{id})","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Required: title, employmentType — A required field is missing; the body lists `fields`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Required: title, employmentType","path":"/business-made/recruitment/board/jobs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job not found — Editing a job that does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Job not found","path":"/business-made/recruitment/board/jobs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Saves a job posting from the hiring screen — with `sk` (or `id`) it edits that job, otherwise it creates one, as a draft unless opened straight away. Returns the job detail.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/board/jobs (body) -> The job detail (as GET /business-made/recruitment/board/jobs/{id})\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | JOB_REQUIRED | Required: title, employmentType | A required field is missing; the body lists `fields`. | — |\n| `404` | JOB_POSTING_NOT_FOUND | Job not found | Editing a job that does not exist. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","employmentType"],"properties":{"sk":{"type":"string"},"title":{"type":"string"},"employmentType":{"type":"string"},"department":{"type":"string"},"location":{"type":"array","items":{"type":"string"}},"workArrangement":{"type":"string","default":"onsite"},"description":{"type":"string"},"openings":{"type":"integer","default":1},"closingDate":{"type":"string"},"salary":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"},"currency":{"type":"string"},"period":{"type":"string","default":"hourly"},"showOnPosting":{"type":"boolean"}}}}},"example":{"title":"Line cook","employmentType":"full-time","department":"Kitchen","location":["harbor-grill"],"openings":2,"salary":{"min":18,"max":22,"period":"hourly"}}}}}}},"/business-made/recruitment/board/jobs/{id}":{"get":{"operationId":"RecruitmentController_jobDetail","summary":"One job with its applicants","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"`{ id, data, title, status, statusLabel, location, pay, openings, hired, moves:[{to, label}], applicants, postedAt, closedReason }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job posting not found — No job posting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Job posting not found","path":"/business-made/recruitment/board/jobs/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"The job's fields, labels, pay, openings and hires, the status moves allowed from where it is (`moves`), and its applicants.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/board/jobs/{id} (id: string) -> `{ id, data, title, status, statusLabel, location, pay, openings, hired, moves:[{to, label}], applicants, postedAt, closedReason }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: \"JOB_POSTING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/recruitment/board/jobs/{id}/status":{"post":{"operationId":"RecruitmentController_setJobStatus","summary":"Move a job to another status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The job detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A Draft job cannot move to Filled — That move is not allowed from the job's status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A Draft job cannot move to Filled","path":"/business-made/recruitment/board/jobs/{id}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job posting not found — No job posting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Job posting not found","path":"/business-made/recruitment/board/jobs/{id}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Only the moves allowed from where the job is: draft → open or cancelled; open → on hold, filled or closed; on hold → open or closed; filled / closed → open; cancelled → draft.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/board/jobs/{id}/status (id: string, body) -> The job detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: \"JOB_POSTING_NOT_FOUND\"` and the id. |\n| `400` | JOB_STATUS_MOVE | A Draft job cannot move to Filled | That move is not allowed from the job's status. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["draft","open","on-hold","filled","closed","cancelled"]},"reason":{"type":"string"}}},"example":{"status":"on-hold","reason":"Budget review"}}}}}},"/business-made/recruitment/board/applicants":{"post":{"operationId":"RecruitmentController_addApplicant","summary":"Add an applicant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The applicant detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Required: firstName, lastName, email — A required field is missing; the body lists `fields`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Required: firstName, lastName, email","path":"/business-made/recruitment/board/applicants","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Puts a person into the pipeline at `new`, for a job or with no job yet. Returns the applicant detail.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/board/applicants (body) -> The applicant detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | APPLICANT_REQUIRED | Required: firstName, lastName, email | A required field is missing; the body lists `fields`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jobPostingId":{"type":"string"},"personalInfo":{"type":"object","required":["firstName","lastName","email"],"properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}},"source":{"type":"string","default":"other"},"notes":{"type":"string"}}},"example":{"jobPostingId":"66f0c3a1e4b0a1b2c3d4e5f6","personalInfo":{"firstName":"Luis","lastName":"Ortega","email":"luis@example.com"},"source":"referral"}}}}}},"/business-made/recruitment/board/applicants/{id}":{"get":{"operationId":"RecruitmentController_applicantDetail","summary":"One applicant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The applicant detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Applicant not found — No applicant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Applicant not found","path":"/business-made/recruitment/board/applicants/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"The person, the job and its status, the current stage, stage history, notes, rejection reason, availability and expected salary, the pipeline stages with which are reached, `canReopen`, and `hireWith` (what the employee form should prefill when hiring).\n\n#### Signature\n\n```http\nGET /business-made/recruitment/board/applicants/{id} (id: string) -> The applicant detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: \"APPLICANT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/recruitment/board/applicants/{id}/action":{"post":{"operationId":"RecruitmentController_applicantAction","summary":"Move an applicant along","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The applicant detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick a pipeline stage (hiring is its own step) — `stage` is not a pipeline stage, or is `hired`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick a pipeline stage (hiring is its own step)","path":"/business-made/recruitment/board/applicants/{id}/action","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Applicant not found — No applicant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Applicant not found","path":"/business-made/recruitment/board/applicants/{id}/action","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"One endpoint for every pipeline step: `stage` (move to `stage`; hiring is its own step), `reject` (with `reason`), `withdraw`, `reopen` (bring a rejected or withdrawn applicant back), `notes` (replace the notes). Returns the applicant detail.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/board/applicants/{id}/action (id: string, body) -> The applicant detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: \"APPLICANT_NOT_FOUND\"` and the id. |\n| `400` | APPLICANT_STAGE | Pick a pipeline stage (hiring is its own step) | `stage` is not a pipeline stage, or is `hired`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["stage","reject","withdraw","reopen","notes"]},"stage":{"type":"string","enum":["new","screening","interview","assessment","offer"]},"reason":{"type":"string"},"notes":{"type":"string"}}},"example":{"action":"stage","stage":"interview"}}}}}},"/business-made/recruitment/board/applicants/{id}/hired":{"post":{"operationId":"RecruitmentController_markHired","summary":"Record a hire","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The applicant detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Which employee record was created? — `employeeId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Which employee record was created?","path":"/business-made/recruitment/board/applicants/{id}/hired","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Applicant not found — No applicant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Applicant not found","path":"/business-made/recruitment/board/applicants/{id}/hired","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Links the applicant to the employee record the people form created, marks them hired, and counts the hire against the job — which becomes `filled` once every opening is taken. Returns the applicant detail.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/board/applicants/{id}/hired (id: string, body) -> The applicant detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: \"APPLICANT_NOT_FOUND\"` and the id. |\n| `400` | EMPLOYEE_REQUIRED | Which employee record was created? | `employeeId` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["employeeId"],"properties":{"employeeId":{"type":"string","description":"bm_employee sk of the new record."}}},"example":{"employeeId":"66f0c3a1e4b0a1b2c3d4e5f7"}}}}}},"/business-made/recruitment/jobs":{"get":{"operationId":"RecruitmentController_getJobPostings","summary":"List job postings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Job postings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Job postings across the org, published or not.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/jobs () -> Job postings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/positions/status/open`"},"post":{"operationId":"RecruitmentController_createJobPosting","summary":"Create a job posting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created posting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Creates a posting as a draft. Link it to an open position so the vacancy and the advert stay connected.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/jobs (body) -> The created posting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/jobs/{id}/publish`","requestBody":{"description":"The posting to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Senior Engineer","positionId":"POS-eng-lead","departmentId":"DEP-eng","description":"Build and run the platform.","location":"london"}}}}}},"/business-made/recruitment/jobs/{id}":{"get":{"operationId":"RecruitmentController_getJobPosting","summary":"Get a job posting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The posting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/recruitment/jobs/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Fetches one posting with its description and status.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/jobs/{id} (id: string) -> The posting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/jobs/{id}/publish`"},"delete":{"operationId":"RecruitmentController_deleteJobPosting","summary":"Delete a job posting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Deletes a posting. Applicants attached to it are not removed, so close it instead where the pipeline matters.\n\n#### Signature\n\n```http\nDELETE /business-made/recruitment/jobs/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/jobs/{id}/close`"}},"/business-made/recruitment/jobs/update":{"post":{"operationId":"RecruitmentController_updateJobPosting","summary":"Update a job posting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated posting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Updates a posting. Editing one already published changes what candidates see immediately. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/jobs/update (body) -> The updated posting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/jobs/{id}/close`","requestBody":{"description":"The posting to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"JOB-4821","data":{"title":"Staff Engineer"}}}}}}},"/business-made/recruitment/jobs/{id}/publish":{"post":{"operationId":"RecruitmentController_publishJobPosting","summary":"Publish a job posting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The published posting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job posting not found — No job posting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Job posting not found","path":"/business-made/recruitment/jobs/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Makes a posting live and open to applications.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/jobs/{id}/publish (id: string) -> The published posting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: \"JOB_POSTING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/jobs/{id}/close`"}},"/business-made/recruitment/jobs/{id}/close":{"post":{"operationId":"RecruitmentController_closeJobPosting","summary":"Close a job posting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The closed posting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job posting not found — No job posting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Job posting not found","path":"/business-made/recruitment/jobs/{id}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Stops new applications while keeping the posting and its pipeline. Candidates already in process are unaffected.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/jobs/{id}/close (id: string, body) -> The closed posting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: \"JOB_POSTING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/metrics/applicants-by-stage`","requestBody":{"description":"Optional detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Position filled"}}}}}},"/business-made/recruitment/applicants":{"get":{"operationId":"RecruitmentController_getApplicants","summary":"List applicants","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","in":"query","required":false,"schema":{"type":"string"},"example":"JOB-4821"},{"name":"stage","in":"query","required":false,"schema":{"type":"string"},"example":"interview"}],"responses":{"200":{"description":"Applicants","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Applicants across the org, filterable by job and stage.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/applicants (jobId?: string, stage?: string) -> Applicants\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Candidate data is personal data held about non-employees — retention limits usually apply.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/metrics/applicants-by-stage`"},"post":{"operationId":"RecruitmentController_createApplicant","summary":"Create an applicant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created applicant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Records an application against a posting.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/applicants (body) -> The created applicant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/applicants/{id}/stage`","requestBody":{"description":"The applicant.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"jobId":"JOB-4821","firstName":"Grace","lastName":"Hopper","email":"grace@example.com","source":"careers-site"}}}}}},"/business-made/recruitment/applicants/{id}":{"get":{"operationId":"RecruitmentController_getApplicant","summary":"Get an applicant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The applicant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/recruitment/applicants/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Fetches one applicant with their stage, notes and interview history.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/applicants/{id} (id: string) -> The applicant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/applicants/{id}/stage`"}},"/business-made/recruitment/applicants/update":{"post":{"operationId":"RecruitmentController_updateApplicant","summary":"Update an applicant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated applicant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Updates an applicant record. Stage changes and rejection have their own endpoints, which record the transition. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/applicants/update (body) -> The updated applicant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/applicants/{id}/stage`","requestBody":{"description":"The applicant to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"APP-4821","data":{"phone":"+15551234567"}}}}}}},"/business-made/recruitment/applicants/{id}/stage":{"post":{"operationId":"RecruitmentController_moveApplicantStage","summary":"Move an applicant to a stage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated applicant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Applicant not found — No applicant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Applicant not found","path":"/business-made/recruitment/applicants/{id}/stage","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Advances an applicant through the hiring pipeline. Each move is recorded, which is what makes time-in-stage measurable.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/applicants/{id}/stage (id: string, body) -> The updated applicant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: \"APPLICANT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/interviews`","requestBody":{"description":"The new stage.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"stage":"interview","note":"Strong screening call"}}}}}},"/business-made/recruitment/applicants/{id}/reject":{"post":{"operationId":"RecruitmentController_rejectApplicant","summary":"Reject an applicant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The rejected applicant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Applicant not found — No applicant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Applicant not found","path":"/business-made/recruitment/applicants/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Records a rejection with a reason. Keeping the reason matters both for candidate feedback and for showing hiring decisions were made on consistent grounds.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/applicants/{id}/reject (id: string, body) -> The rejected applicant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: \"APPLICANT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/applicants/{id}/notes`","requestBody":{"description":"Why they were rejected.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Insufficient experience with distributed systems","notifyCandidate":true}}}}}},"/business-made/recruitment/applicants/{id}/notes":{"post":{"operationId":"RecruitmentController_addApplicantNote","summary":"Add a note to an applicant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated applicant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Applicant not found — No applicant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Applicant not found","path":"/business-made/recruitment/applicants/{id}/notes","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Adds an internal note. Assume a candidate could one day request their record — write notes about evidence and fit, not about the person.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/applicants/{id}/notes (id: string, body) -> The updated applicant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Subject-access requests can include recruitment notes.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: \"APPLICANT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/applicants/{id}`","requestBody":{"description":"The note.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Strong systems design; limited exposure to our stack"}}}}}},"/business-made/recruitment/interviews":{"get":{"operationId":"RecruitmentController_getInterviews","summary":"List interviews","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"applicantId","in":"query","required":false,"schema":{"type":"string"},"example":"APP-4821"}],"responses":{"200":{"description":"Interviews","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Scheduled and completed interviews.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/interviews (applicantId?: string) -> Interviews\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/interviews`"},"post":{"operationId":"RecruitmentController_scheduleInterview","summary":"Schedule an interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The scheduled interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Schedules an interview for an applicant, with its panel and format.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/interviews (body) -> The scheduled interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/interviews/{id}/reschedule`","requestBody":{"description":"The interview to schedule.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"applicantId":"APP-4821","scheduledAt":"2026-10-08T13:00:00.000Z","type":"technical","interviewers":["EMP-4001"]}}}}}},"/business-made/recruitment/interviews/{id}":{"get":{"operationId":"RecruitmentController_getInterview","summary":"Get an interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/recruitment/interviews/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Fetches one interview with its panel and feedback.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/interviews/{id} (id: string) -> The interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/interviews/{id}/complete`"}},"/business-made/recruitment/interviews/update":{"post":{"operationId":"RecruitmentController_updateInterview","summary":"Update an interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Updates an interview. Use `reschedule` when the time changes — it records the move rather than silently overwriting it. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/interviews/update (body) -> The updated interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/interviews/{id}/reschedule`","requestBody":{"description":"The interview to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"INT-4821","data":{"interviewers":["EMP-4001","EMP-4002"]}}}}}}},"/business-made/recruitment/interviews/{id}/complete":{"post":{"operationId":"RecruitmentController_completeInterview","summary":"Complete an interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The completed interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Interview not found — No interview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Interview not found","path":"/business-made/recruitment/interviews/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Records that an interview happened, with the panel's feedback and recommendation.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/interviews/{id}/complete (id: string, body) -> The completed interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INTERVIEW_NOT_FOUND | Interview not found | No interview has that id. | The body carries `code: \"INTERVIEW_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/applicants/{id}/stage`","requestBody":{"description":"The outcome.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"recommendation":"advance","feedback":"Strong design discussion; would hire"}}}}}},"/business-made/recruitment/interviews/{id}/cancel":{"post":{"operationId":"RecruitmentController_cancelInterview","summary":"Cancel an interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Interview not found — No interview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Interview not found","path":"/business-made/recruitment/interviews/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Cancels a scheduled interview, keeping the record and the reason.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/interviews/{id}/cancel (id: string, body) -> The cancelled interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INTERVIEW_NOT_FOUND | Interview not found | No interview has that id. | The body carries `code: \"INTERVIEW_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/interviews/{id}/reschedule`","requestBody":{"description":"Why it was cancelled.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Candidate withdrew"}}}}}},"/business-made/recruitment/interviews/{id}/reschedule":{"post":{"operationId":"RecruitmentController_rescheduleInterview","summary":"Reschedule an interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The rescheduled interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Interview not found — No interview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Interview not found","path":"/business-made/recruitment/interviews/{id}/reschedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Moves an interview to a new time, recording that it was rescheduled rather than overwriting the original slot.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/interviews/{id}/reschedule (id: string, body) -> The rescheduled interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | INTERVIEW_NOT_FOUND | Interview not found | No interview has that id. | The body carries `code: \"INTERVIEW_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/interviews/{id}/cancel`","requestBody":{"description":"The new time.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"scheduledAt":"2026-10-10T13:00:00.000Z","reason":"Panel conflict"}}}}}},"/business-made/recruitment/offers":{"get":{"operationId":"RecruitmentController_getOffers","summary":"List offers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Offers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Offers made, at any stage.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/offers () -> Offers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/offers/{id}`"},"post":{"operationId":"RecruitmentController_createOffer","summary":"Create an offer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The drafted offer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Drafts an offer for an applicant. It is not sent until `send`, so terms can be agreed internally first.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/offers (body) -> The drafted offer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/offers/{id}/send`","requestBody":{"description":"The offer to draft.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"applicantId":"APP-4821","salary":88000,"startDate":"2026-11-03","gradeId":"GRD-g7"}}}}}},"/business-made/recruitment/offers/{id}":{"get":{"operationId":"RecruitmentController_getOffer","summary":"Get an offer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The offer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/recruitment/offers/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Fetches one offer with its terms and current status.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/offers/{id} (id: string) -> The offer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/offers/{id}/send`"}},"/business-made/recruitment/offers/update":{"post":{"operationId":"RecruitmentController_updateOffer","summary":"Update an offer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated offer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Updates a draft offer. Changing terms after sending means re-sending — the candidate has already seen the previous version. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/offers/update (body) -> The updated offer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/offers/{id}/send`","requestBody":{"description":"The offer to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"OFR-4821","data":{"salary":92000}}}}}}},"/business-made/recruitment/offers/{id}/send":{"post":{"operationId":"RecruitmentController_sendOffer","summary":"Send an offer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The sent offer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offer not found — No offer has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offer not found","path":"/business-made/recruitment/offers/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Sends the offer to the candidate. This is the point the terms become a commitment they can accept, so confirm them first.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/offers/{id}/send (id: string) -> The sent offer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: \"OFFER_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/offers/{id}/accept`"}},"/business-made/recruitment/offers/{id}/accept":{"post":{"operationId":"RecruitmentController_acceptOffer","summary":"Record an accepted offer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The accepted offer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offer not found — No offer has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offer not found","path":"/business-made/recruitment/offers/{id}/accept","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Records that the candidate accepted. The handover point to onboarding — create the employee and start their checklist next.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/offers/{id}/accept (id: string, body) -> The accepted offer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: \"OFFER_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees`\n- `POST /business-made/onboarding/{employeeId}`","requestBody":{"description":"Optional detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"acceptedDate":"2026-10-15","confirmedStartDate":"2026-11-03"}}}}}},"/business-made/recruitment/offers/{id}/decline":{"post":{"operationId":"RecruitmentController_declineOffer","summary":"Record a declined offer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The declined offer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offer not found — No offer has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offer not found","path":"/business-made/recruitment/offers/{id}/decline","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Records that the candidate declined, with their reason where given. Decline reasons are the most useful signal about whether offers are competitive.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/offers/{id}/decline (id: string, body) -> The declined offer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: \"OFFER_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/metrics`","requestBody":{"description":"Why they declined.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Accepted a higher offer elsewhere"}}}}}},"/business-made/recruitment/offers/{id}/withdraw":{"post":{"operationId":"RecruitmentController_withdrawOffer","summary":"Withdraw an offer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The withdrawn offer","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offer not found — No offer has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offer not found","path":"/business-made/recruitment/offers/{id}/withdraw","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Withdraws an offer already sent — the employer-side counterpart to decline. Record the reason: withdrawing a made offer is a decision that gets scrutinised.\n\n#### Signature\n\n```http\nPOST /business-made/recruitment/offers/{id}/withdraw (id: string, body) -> The withdrawn offer\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: \"OFFER_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/recruitment/offers/{id}/decline`","requestBody":{"description":"Why it was withdrawn.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Headcount frozen"}}}}}},"/business-made/recruitment/metrics":{"get":{"operationId":"RecruitmentController_getRecruitmentMetrics","summary":"Get recruitment metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Recruitment metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Hiring figures — time to hire, offer acceptance rate and source effectiveness.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/metrics () -> Recruitment metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/metrics/applicants-by-stage`"}},"/business-made/recruitment/metrics/applicants-by-stage":{"get":{"operationId":"RecruitmentController_getApplicantsByStage","summary":"Get applicants by stage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobPostingId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Applicants per stage","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Recruitment"],"description":"Pipeline volume at each stage — where candidates accumulate, and therefore where the bottleneck is.\n\n#### Signature\n\n```http\nGET /business-made/recruitment/metrics/applicants-by-stage () -> Applicants per stage\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/recruitment/metrics`"}},"/business-made/benefits/view/plans":{"get":{"operationId":"BenefitsController_viewPlans","summary":"Benefit plans for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"}],"responses":{"200":{"description":"`{ rows, counts, summary:{activePlans, enrolledPeople, pendingEnrollments, employerAnnualLabel, employeeAnnualLabel} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Every plan as a finished row (active first, then by title) with counts by status and org totals: active plans, people enrolled, pending enrollments, and what employer and employees pay a year. `status` filters the rows.\n\n#### Signature\n\n```http\nGET /business-made/benefits/view/plans (status?: string) -> `{ rows, counts, summary:{activePlans, enrolledPeople, pendingEnrollments, employerAnnualLabel, employeeAnnualLabel} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/benefits/view/plans/{id}":{"get":{"operationId":"BenefitsController_viewPlan","summary":"One benefit plan for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The plan detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Benefit plan not found. — No benefit plan has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Benefit plan not found.","path":"/business-made/benefits/view/plans/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"The plan row plus description, provider, the coverage tiers with employee and employer cost labels, the key terms (deductible, out-of-pocket max, coinsurance, copay, waiting period, who can join, minimum hours, plan year, dates), notes, the enrollments on it, and `offeredTiers` for an enroll form.\n\n#### Signature\n\n```http\nGET /business-made/benefits/view/plans/{id} (id: string) -> The plan detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BENEFIT_PLAN_NOT_FOUND | Benefit plan not found. | No benefit plan has that id. | The body carries `code: \"BENEFIT_PLAN_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/benefits/view/enrollments":{"get":{"operationId":"BenefitsController_viewEnrollments","summary":"Benefit enrollments for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"pending"}],"responses":{"200":{"description":"`{ rows, counts, activePlans:[{value, label, tiers}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Every enrollment as a finished row with counts by status, and `activePlans` (with their tiers) for the enroll form. `status` filters the rows.\n\n#### Signature\n\n```http\nGET /business-made/benefits/view/enrollments (status?: string) -> `{ rows, counts, activePlans:[{value, label, tiers}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/benefits/view/enrollments/{id}":{"get":{"operationId":"BenefitsController_viewEnrollment","summary":"One benefit enrollment for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The enrollment detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found. — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found.","path":"/business-made/benefits/view/enrollments/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"The enrollment row plus its type, the payroll-deduction label, dependents and beneficiaries, the termination or waiver reason, and notes.\n\n#### Signature\n\n```http\nGET /business-made/benefits/view/enrollments/{id} (id: string) -> The enrollment detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found. | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/benefits/plans":{"get":{"operationId":"BenefitsController_getBenefitPlans","summary":"List benefit plans","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Benefit plans","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Every benefit plan defined for the org, active or not.\n\n#### Signature\n\n```http\nGET /business-made/benefits/plans () -> Benefit plans\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/benefits/plans/active`"},"post":{"operationId":"BenefitsController_createBenefitPlan","summary":"Create a benefit plan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created plan","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Give the plan a name. — `title` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give the plan a name.","path":"/business-made/benefits/plans","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Defines a benefit plan. Its employee-contribution figures feed payroll deductions, so they must match what the carrier actually charges.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/plans (body) -> The created plan\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PLAN_TITLE_REQUIRED | Give the plan a name. | `title` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/plans/{id}/activate`","requestBody":{"description":"The plan to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"HEALTH-PPO","name":"Health PPO","carrier":"Acme Health","employeeCost":120,"employerCost":380,"coverageType":"medical"}}}}}},"/business-made/benefits/plans/active":{"get":{"operationId":"BenefitsController_getActivePlans","summary":"List active benefit plans","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active plans","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Plans currently available to enrol in — what an enrolment screen should offer.\n\n#### Signature\n\n```http\nGET /business-made/benefits/plans/active () -> Active plans\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments`"}},"/business-made/benefits/plans/{id}":{"get":{"operationId":"BenefitsController_getBenefitPlan","summary":"Get a benefit plan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The plan","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/benefits/plans/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Fetches one plan with its coverage, costs and eligibility rules.\n\n#### Signature\n\n```http\nGET /business-made/benefits/plans/{id} (id: string) -> The plan\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/plans/update`"},"delete":{"operationId":"BenefitsController_deleteBenefitPlan","summary":"Delete a benefit plan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"People have enrolled in this plan — deactivate it instead of deleting it. — The plan has enrollments.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"People have enrolled in this plan — deactivate it instead of deleting it.","path":"/business-made/benefits/plans/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Benefit plan not found — No benefit plan has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Benefit plan not found","path":"/business-made/benefits/plans/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Deletes a plan. Existing enrolments are not terminated — deactivate it instead to stop new enrolments while honouring current ones.\n\n#### Signature\n\n```http\nDELETE /business-made/benefits/plans/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Orphans active enrolments.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BENEFIT_PLAN_NOT_FOUND | Benefit plan not found | No benefit plan has that id. | The body carries `code: \"BENEFIT_PLAN_NOT_FOUND\"` and the id. |\n| `400` | PLAN_IN_USE | People have enrolled in this plan — deactivate it instead of deleting it. | The plan has enrollments. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/plans/{id}/deactivate`"}},"/business-made/benefits/plans/update":{"post":{"operationId":"BenefitsController_updateBenefitPlan","summary":"Update a benefit plan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated plan","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Give the plan a name. — `title` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give the plan a name.","path":"/business-made/benefits/plans/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Benefit plan not found — No benefit plan has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Benefit plan not found","path":"/business-made/benefits/plans/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Edits a plan (body: `sk` plus `data`; the plan keeps its status and the same checks as create run). Changing contribution amounts affects payroll deductions from the next run — existing enrolments are not re-priced retrospectively.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/plans/update (body) -> The updated plan\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Tell employees before their deduction changes.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BENEFIT_PLAN_NOT_FOUND | Benefit plan not found | No benefit plan has that id. | The body carries `code: \"BENEFIT_PLAN_NOT_FOUND\"` and the id. |\n| `400` | PLAN_TITLE_REQUIRED | Give the plan a name. | `title` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/benefits/enrollments`","requestBody":{"description":"The plan to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f0c3a1e4b0a1b2c3d4e5f6","data":{"title":"Health — PPO","coverage":{"tiers":[{"tier":"employee","employeeCost":135,"employerCost":420,"frequency":"monthly"}]}}}}}}}},"/business-made/benefits/plans/{id}/activate":{"post":{"operationId":"BenefitsController_activateBenefitPlan","summary":"Activate a benefit plan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The activated plan","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Add at least one coverage level with its cost before switching the plan on. — The plan has no coverage tiers.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Add at least one coverage level with its cost before switching the plan on.","path":"/business-made/benefits/plans/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Benefit plan not found — No benefit plan has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Benefit plan not found","path":"/business-made/benefits/plans/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Makes a plan available to enrol in.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/plans/{id}/activate (id: string) -> The activated plan\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BENEFIT_PLAN_NOT_FOUND | Benefit plan not found | No benefit plan has that id. | The body carries `code: \"BENEFIT_PLAN_NOT_FOUND\"` and the id. |\n| `400` | PLAN_TIERS_REQUIRED | Add at least one coverage level with its cost before switching the plan on. | The plan has no coverage tiers. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/plans/{id}/open-enrollment`"}},"/business-made/benefits/plans/{id}/deactivate":{"post":{"operationId":"BenefitsController_deactivateBenefitPlan","summary":"Deactivate a benefit plan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The deactivated plan","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Benefit plan not found — No benefit plan has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Benefit plan not found","path":"/business-made/benefits/plans/{id}/deactivate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Stops new enrolments while leaving existing ones in force. The reversible way to retire a plan without cutting anyone off mid-year.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/plans/{id}/deactivate (id: string) -> The deactivated plan\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BENEFIT_PLAN_NOT_FOUND | Benefit plan not found | No benefit plan has that id. | The body carries `code: \"BENEFIT_PLAN_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/plans/{id}/activate`"}},"/business-made/benefits/plans/{id}/open-enrollment":{"post":{"operationId":"BenefitsController_startOpenEnrollment","summary":"Open enrolment for a plan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated plan","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick the first and last day of the enrollment window. — A window date is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick the first and last day of the enrollment window.","path":"/business-made/benefits/plans/{id}/open-enrollment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Benefit plan not found — No benefit plan has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Benefit plan not found","path":"/business-made/benefits/plans/{id}/open-enrollment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Opens an enrolment window — the period during which employees may join or change plans. Outside it, enrolment normally requires a qualifying life event.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/plans/{id}/open-enrollment (id: string, body) -> The updated plan\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BENEFIT_PLAN_NOT_FOUND | Benefit plan not found | No benefit plan has that id. | The body carries `code: \"BENEFIT_PLAN_NOT_FOUND\"` and the id. |\n| `400` | WINDOW_REQUIRED | Pick the first and last day of the enrollment window. | A window date is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments`","requestBody":{"description":"The enrolment window.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"startDate":"2026-11-01","endDate":"2026-11-30","effectiveDate":"2027-01-01"}}}}}},"/business-made/benefits/enrollments":{"get":{"operationId":"BenefitsController_getEnrollments","summary":"List benefit enrolments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Enrolments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Enrolments across the org. These identify who has which coverage — sensitive personal data, particularly for medical plans.\n\n#### Signature\n\n```http\nGET /business-made/benefits/enrollments () -> Enrolments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Medical enrolment data is special-category personal data in many jurisdictions.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/benefits/enrollments/employee/{employeeId}`"},"post":{"operationId":"BenefitsController_createEnrollment","summary":"Create a benefit enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick the person to enroll. — No employee is named.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick the person to enroll.","path":"/business-made/benefits/enrollments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Enrols an employee in a plan. The enrolment starts pending — activation is what makes coverage effective and starts the payroll deduction.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/enrollments (body) -> The created enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMPLOYEE_REQUIRED | Pick the person to enroll. | No employee is named. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments/{id}/activate`","requestBody":{"description":"The enrolment to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","planId":"PLN-health","coverageLevel":"employee-plus-spouse","effectiveDate":"2027-01-01"}}}}}},"/business-made/benefits/enrollments/{id}":{"get":{"operationId":"BenefitsController_getEnrollment","summary":"Get a benefit enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/benefits/enrollments/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Fetches one enrolment with its dependants and beneficiaries.\n\n#### Signature\n\n```http\nGET /business-made/benefits/enrollments/{id} (id: string) -> The enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments/{id}/activate`"},"delete":{"operationId":"BenefitsController_deleteEnrollment","summary":"Delete a benefit enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Coverage that started is part of the record — end it instead. — The enrollment is not pending.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Coverage that started is part of the record — end it instead.","path":"/business-made/benefits/enrollments/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Deletes an enrolment record. Terminate instead where the person genuinely had coverage — the record matters for claims and continuation rights.\n\n#### Signature\n\n```http\nDELETE /business-made/benefits/enrollments/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n| `400` | KEEP_HISTORY | Coverage that started is part of the record — end it instead. | The enrollment is not pending. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments/{id}/terminate`"}},"/business-made/benefits/enrollments/update":{"post":{"operationId":"BenefitsController_updateEnrollment","summary":"Update a benefit enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Updates an enrolment — changing coverage level, for instance. Outside an open-enrolment window this normally requires a qualifying life event, which this endpoint does not enforce. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/enrollments/update (body) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Eligibility rules are not enforced here — check them before changing coverage.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/plans/{id}/open-enrollment`","requestBody":{"description":"The enrolment to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"ENR-4821","data":{"coverageLevel":"family"}}}}}}},"/business-made/benefits/enrollments/{id}/activate":{"post":{"operationId":"BenefitsController_activateEnrollment","summary":"Activate an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The activated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a pending enrollment can be made active. — The enrollment is not pending.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a pending enrollment can be made active.","path":"/business-made/benefits/enrollments/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Makes coverage effective and starts the associated payroll deduction. The point the employee is genuinely covered.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/enrollments/{id}/activate (id: string) -> The activated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n| `400` | NOT_PENDING | Only a pending enrollment can be made active. | The enrollment is not pending. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments/{id}/terminate`"}},"/business-made/benefits/enrollments/{id}/terminate":{"post":{"operationId":"BenefitsController_terminateEnrollment","summary":"Terminate an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The terminated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only active coverage can be ended. — The enrollment is not active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only active coverage can be ended.","path":"/business-made/benefits/enrollments/{id}/terminate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}/terminate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Ends coverage and stops the payroll deduction, keeping the record.\n\nThe termination date matters: coverage often runs to the end of a month rather than a leaving date, and continuation rights may follow. Set it deliberately rather than accepting a default.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/enrollments/{id}/terminate (id: string, body) -> The terminated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n| `400` | NOT_ACTIVE | Only active coverage can be ended. | The enrollment is not active. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/complete`","requestBody":{"description":"Termination details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"terminationDate":"2026-09-30","reason":"Employment ended"}}}}}},"/business-made/benefits/enrollments/{id}/waive":{"post":{"operationId":"BenefitsController_waiveEnrollment","summary":"Waive coverage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The waived enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a pending enrollment can be waived. — The enrollment is not pending.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a pending enrollment can be waived.","path":"/business-made/benefits/enrollments/{id}/waive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}/waive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Records that an employee declined a benefit. Worth capturing explicitly — a documented waiver is different from never having been offered, and it is the difference that matters if it is ever questioned.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/enrollments/{id}/waive (id: string, body) -> The waived enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n| `400` | NOT_PENDING | Only a pending enrollment can be waived. | The enrollment is not pending. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments/{id}/activate`","requestBody":{"description":"Waiver details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Covered under spouse's plan"}}}}}},"/business-made/benefits/enrollments/employee/{employeeId}":{"get":{"operationId":"BenefitsController_getEmployeeEnrollments","summary":"Get an employee's enrolments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The employee's enrolments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"One employee's benefit enrolments — what a self-service benefits screen shows.\n\n#### Signature\n\n```http\nGET /business-made/benefits/enrollments/employee/{employeeId} (employeeId: string) -> The employee's enrolments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments`"}},"/business-made/benefits/enrollments/{id}/dependents":{"post":{"operationId":"BenefitsController_addDependent","summary":"Add a dependant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick how they are related. — `relationship` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick how they are related.","path":"/business-made/benefits/enrollments/{id}/dependents","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}/dependents","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Adds a dependant to an enrolment. Dependant details are personal data about people who are not employees — hold only what the plan requires.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/enrollments/{id}/dependents (id: string, body) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n| `400` | DEPENDENT_RELATIONSHIP_REQUIRED | Pick how they are related. | `relationship` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/benefits/enrollments/{id}/dependents/{dependentId}`","requestBody":{"description":"The dependant.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Byron Lovelace","relationship":"child","dateOfBirth":"2018-04-12"}}}}}},"/business-made/benefits/enrollments/{id}/dependents/{dependentId}":{"delete":{"operationId":"BenefitsController_removeDependent","summary":"Remove a dependant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"dependentId","required":true,"in":"path","schema":{"type":"string"},"description":"Dependant id.","example":"DEP-4821"}],"responses":{"200":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}/dependents/{dependentId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Removes a dependant from an enrolment. Coverage level may need adjusting separately — removing a dependant does not re-price the enrolment.\n\n#### Signature\n\n```http\nDELETE /business-made/benefits/enrollments/{id}/dependents/{dependentId} (id: string, dependentId: string) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The coverage level and its cost are unchanged — update them if the tier should drop.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments/update`"}},"/business-made/benefits/enrollments/{id}/beneficiaries":{"post":{"operationId":"BenefitsController_addBeneficiary","summary":"Add a beneficiary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Enter their share as a percentage between 1 and 100. — `percentage` is missing or out of range.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Enter their share as a percentage between 1 and 100.","path":"/business-made/benefits/enrollments/{id}/beneficiaries","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}/beneficiaries","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Adds a beneficiary to an enrolment — who receives a benefit on death. Accuracy matters more here than almost anywhere else in the module, and it is rarely revisited once set.\n\n#### Signature\n\n```http\nPOST /business-made/benefits/enrollments/{id}/beneficiaries (id: string, body) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Nothing checks that percentages total 100 — verify before relying on the allocation.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n| `400` | BENEFICIARY_SHARE_REQUIRED | Enter their share as a percentage between 1 and 100. | `percentage` is missing or out of range. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/benefits/enrollments/{id}/beneficiaries/{beneficiaryId}`","requestBody":{"description":"The beneficiary.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Grace Hopper","relationship":"spouse","percentage":100}}}}}},"/business-made/benefits/enrollments/{id}/beneficiaries/{beneficiaryId}":{"delete":{"operationId":"BenefitsController_removeBeneficiary","summary":"Remove a beneficiary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"beneficiaryId","required":true,"in":"path","schema":{"type":"string"},"description":"Beneficiary id.","example":"BEN-4821"}],"responses":{"200":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrollment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/benefits/enrollments/{id}/beneficiaries/{beneficiaryId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Removes a beneficiary. Removing one without adding a replacement can leave the allocation summing to less than 100%.\n\n#### Signature\n\n```http\nDELETE /business-made/benefits/enrollments/{id}/beneficiaries/{beneficiaryId} (id: string, beneficiaryId: string) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrollment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/benefits/enrollments/{id}/beneficiaries`"}},"/business-made/benefits/metrics":{"get":{"operationId":"BenefitsController_getBenefitsMetrics","summary":"Get benefits metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Benefits metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Benefits"],"description":"Aggregate benefits figures — take-up rates, cost by plan and employer contribution totals.\n\n#### Signature\n\n```http\nGET /business-made/benefits/metrics () -> Benefits metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/benefits/enrollments`"}},"/business-made/leave/setup/readiness":{"get":{"operationId":"LeaveController_readiness","summary":"Is the Leave module ready","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ state, enabled, enabledAt?, enabledBy?, missing:[{key,label,route}], warnings:[{key,label,route}], counts:{types,policies,employees,hr,routeToHr,onDefault,unmatched}, flow, unit }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"state":"incomplete","enabled":true,"missing":[{"key":"policy","label":"Mark one active policy as the org default","route":"/hr/time-off/setup#policies"}],"warnings":[],"counts":{"types":3,"policies":1,"employees":24,"hr":1},"flow":"supervisor-hr","unit":"days"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"One answer for every client: `state` is `off` (not switched on), `incomplete` (on, but something required is missing) or `ready`. `missing` lists what blocks it (basics saved, a leave type, a default policy, who is HR, the approval flow), `warnings` what is worth fixing (people routing to HR, unmapped job titles, locations with no country, no holidays this year). Each item carries a label and the setup-page route.\n\n#### Signature\n\n```http\nGET /business-made/leave/setup/readiness () -> `{ state, enabled, enabledAt?, enabledBy?, missing:[{key,label,route}], warnings:[{key,label,route}], counts:{types,policies,employees,hr,routeToHr,onDefault,unmatched}, flow, unit }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/setup`\n- `POST /business-made/leave/setup/enable`"}},"/business-made/leave/setup":{"get":{"operationId":"LeaveController_getSetup","summary":"Everything the setup page shows","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Location sk or slug; `all` or empty = org level.","example":"harbor-grill"}],"responses":{"200":{"description":"`{ settings, readiness, hr, suggestion, types, policies, levels:{ladder,counts,titles}, resolution:[{sk,employeeId,name,level,policyId,policyTitle,viaDefault,approver,…}], locations, calendar, year, departments, employmentTypes }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Settings, readiness, who is HR, a suggested approval flow, every leave type and policy (with how many people each policy covers), the job-level ladder with title counts, how every person resolves (level, policy, approver), locations with their country, this year's calendar days, and the departments and employment types in use. `businessLocationId` shows that location's settings on top of the org's.\n\n#### Signature\n\n```http\nGET /business-made/leave/setup (businessLocationId?: string) -> `{ settings, readiness, hr, suggestion, types, policies, levels:{ladder,counts,titles}, resolution:[{sk,employeeId,name,level,policyId,policyTitle,viaDefault,approver,…}], locations, calendar, year, departments, employmentTypes }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/setup/settings`"}},"/business-made/leave/setup/enable":{"post":{"operationId":"LeaveController_enable","summary":"Switch Leave on or off","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The module readiness","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Turns the module on (default) or off. Returns the readiness after the change.\n\n#### Signature\n\n```http\nPOST /business-made/leave/setup/enable (body) -> The module readiness\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/setup/readiness`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","default":true}}},"example":{"enabled":true}}}}}},"/business-made/leave/setup/starter":{"post":{"operationId":"LeaveController_starter","summary":"Create the starter types and default policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ types: <created count>, policy: <created policy or null>, retired?, policyBackfilled?, readiness }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"First-run help: creates the starter leave types (`types: true`) and/or a default policy (`policy: true`). **Never overwrites** — a code the org already has is left alone.\n\n#### Signature\n\n```http\nPOST /business-made/leave/setup/starter (body) -> `{ types: <created count>, policy: <created policy or null>, retired?, policyBackfilled?, readiness }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"types":{"type":"boolean"},"policy":{"type":"boolean"}}},"example":{"types":true,"policy":true}}}}}},"/business-made/leave/setup/settings":{"get":{"operationId":"LeaveController_getSettings","summary":"Get leave settings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Location sk or slug.","example":"harbor-grill"}],"responses":{"200":{"description":"The merged settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"unit":"days","leaveYear":{"type":"calendar"},"workingDays":["mon","tue","wed","thu","fri"],"hrUsers":[],"approval":{"baseFlow":"supervisor-hr","escalateAfterDays":3,"dailyDigest":true},"jobLevels":[],"holidaySource":"suggest","saved":true,"location":{"slug":null,"rollup":true,"count":2,"overrides":[]}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"The org's leave settings with defaults filled in: `unit` (days/hours), `leaveYear`, `workingDays`, `hrUsers`, `approval` (`baseFlow`, `escalateAfterDays`, `dailyDigest`), `jobLevels`, `holidaySource`. With a location, its overrides of `workingDays`, `hrUsers` and `holidaySource` are applied and listed in `location.overrides`. `saved` is false until the basics are saved once.\n\n#### Signature\n\n```http\nGET /business-made/leave/setup/settings (businessLocationId?: string) -> The merged settings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"post":{"operationId":"LeaveController_saveSettings","summary":"Save leave settings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"harbor-grill"}],"responses":{"201":{"description":"The merged settings after the save","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Merges the patch into the org settings — or, with a location, saves only the keys a location may override (`workingDays`, `hrUsers`, `holidaySource`). Afterwards every person's level and policy are re-stamped, and the approval escalation (`approval.escalateAfterDays`) is written onto the leave-approval workflow.\n\n#### Signature\n\n```http\nPOST /business-made/leave/setup/settings (businessLocationId?: string, body) -> The merged settings after the save\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/setup/recompute`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"unit":"days","approval":{"baseFlow":"supervisor-hr","escalateAfterDays":2}}}}}}},"/business-made/leave/setup/recompute":{"post":{"operationId":"LeaveController_recompute","summary":"Re-stamp levels and policies on everyone","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ stamped, unchanged, onDefault, unmatched, employees }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Works out each person's job level and leave policy from the settings and writes them to `employment.jobLevel` / `employment.leavePolicyId`, so balances and payroll know which policy applies. Runs on its own after a settings save.\n\n#### Signature\n\n```http\nPOST /business-made/leave/setup/recompute () -> `{ stamped, unchanged, onDefault, unmatched, employees }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/setup/supervisor":{"post":{"operationId":"LeaveController_supervisor","summary":"Set a person's supervisor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ employeeId, supervisor, supervisorName }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Someone cannot be their own supervisor — The supervisor code is the person's own.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Someone cannot be their own supervisor","path":"/business-made/leave/setup/supervisor","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that sk.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/leave/setup/supervisor","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Writes `employment.supervisor` (the supervisor's employee code) and mirrors their direct reports. `supervisor: null` clears it. Leave approvals route to this person.\n\n#### Signature\n\n```http\nPOST /business-made/leave/setup/supervisor (body) -> `{ employeeId, supervisor, supervisorName }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that sk. | Check `employeeSk`. |\n| `400` | SELF_SUPERVISOR | Someone cannot be their own supervisor | The supervisor code is the person's own. | Pick someone else. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["employeeSk"],"properties":{"employeeSk":{"type":"string","description":"bm_employee sk"},"supervisor":{"type":"string","nullable":true,"description":"Supervisor's employee code; null clears."}}},"example":{"employeeSk":"66f0c3a1e4b0a1b2c3d4e5f6","supervisor":"EMP-1001"}}}}}},"/business-made/leave/setup/opening-balances":{"get":{"operationId":"LeaveController_openingBalances","summary":"Preview opening balances","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":false,"in":"query","schema":{"type":"integer"},"description":"Default: the current year.","example":2026}],"responses":{"200":{"description":"`{ year, unit, rows:[{employeeId, leaveTypeId, entitled, accrued, used, existing, changed, …}], summary:{people, rows, new, changed} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"What each person's balance per leave type would be for the year under their policy — entitled and accrued so far — next to what exists. Nothing is saved.\n\n#### Signature\n\n```http\nGET /business-made/leave/setup/opening-balances (year?: integer) -> `{ year, unit, rows:[{employeeId, leaveTypeId, entitled, accrued, used, existing, changed, …}], summary:{people, rows, new, changed} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/setup/opening-balances`"},"post":{"operationId":"LeaveController_commitOpeningBalances","summary":"Commit opening balances","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ year, created, updated }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Commits the preview: creates missing balances and refreshes entitled/accrued on existing ones. **Used and pending are never touched** except where an override sets `used` on a new balance.\n\n#### Signature\n\n```http\nPOST /business-made/leave/setup/opening-balances (body) -> `{ year, created, updated }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"year":{"type":"integer"},"overrides":{"type":"array","items":{"type":"object","properties":{"employeeId":{"type":"string"},"leaveTypeId":{"type":"string"},"used":{"type":"number"}}}}}},"example":{"year":2026,"overrides":[{"employeeId":"EMP-1043","leaveTypeId":"LT-annual","used":3}]}}}}}},"/business-made/leave/calendar/suggest":{"get":{"operationId":"LeaveController_suggestHolidays","summary":"Suggest public holidays","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":false,"in":"query","schema":{"type":"integer"},"description":"Default: this year.","example":2026},{"name":"location","required":false,"in":"query","schema":{"type":"string"},"description":"Location slug.","example":"harbor-grill"}],"responses":{"200":{"description":"`{ year, suggestions:[{location, date, title, sourceId, paid, …}], skipped, locations }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Proposes the year's public holidays for each location (or one) from its country. **Nothing is saved** — keep the ones you want with calendar/accept. Locations with no country are skipped.\n\n#### Signature\n\n```http\nGET /business-made/leave/calendar/suggest (year?: integer, location?: string) -> `{ year, suggestions:[{location, date, title, sourceId, paid, …}], skipped, locations }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/calendar/accept`"}},"/business-made/leave/calendar/accept":{"post":{"operationId":"LeaveController_acceptHolidays","summary":"Keep suggested holidays","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ created, skipped }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Creates the ticked suggestions. A suggestion already imported (same `sourceId`) is skipped, so re-running never undoes an edit.\n\n#### Signature\n\n```http\nPOST /business-made/leave/calendar/accept (body) -> `{ created, skipped }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","properties":{"location":{"type":"string"},"date":{"type":"string"},"title":{"type":"string"},"sourceId":{"type":"string"},"paid":{"type":"boolean"}}}}}},"example":{"rows":[{"location":"harbor-grill","date":"2026-07-04","title":"Independence Day","sourceId":"US-2026-07-04"}]}}}}}},"/business-made/leave/team":{"get":{"operationId":"LeaveController_team","summary":"Who is off when","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"example":"2026-10-01"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"example":"2026-10-31"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Location; `all` = every site.","example":"harbor-grill"}],"responses":{"200":{"description":"`{ from, to, location, sites, people, days:[{date, off:[…], bySite, warning?}], warnAt, requests }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"The team-off calendar: approved and pending requests per site and day, with a coverage warning when a site would have more than 30% of its people off on a day. Default window: `from` today, 30 days.\n\n#### Signature\n\n```http\nGET /business-made/leave/team (from?: string, to?: string, businessLocationId?: string) -> `{ from, to, location, sites, people, days:[{date, off:[…], bySite, warning?}], warnAt, requests }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/insights":{"get":{"operationId":"LeaveController_insights","summary":"Leave liability and usage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":false,"in":"query","schema":{"type":"integer"},"example":2026},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"harbor-grill"}],"responses":{"200":{"description":"`{ year, unit, location, liability, owed:{amount, unit, types}, usage:[{month, requests, amount}], requests:{total, pending, approved, rejected, …}, decisionHours }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Read-only numbers for HR, in the org's unit: accrued liability, what is owed per type, usage by month, request counts by status and the average hours to a decision.\n\n#### Signature\n\n```http\nGET /business-made/leave/insights (year?: integer, businessLocationId?: string) -> `{ year, unit, location, liability, owed:{amount, unit, types}, usage:[{month, requests, amount}], requests:{total, pending, approved, rejected, …}, decisionHours }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/balances-grid":{"get":{"operationId":"LeaveController_balancesGrid","summary":"Balances grid","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":false,"in":"query","schema":{"type":"integer"},"example":2026},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"harbor-grill"}],"responses":{"200":{"description":"`{ year, unit, types, rows, people }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"One row per current person, one cell per active leave type, for the year.\n\n#### Signature\n\n```http\nGET /business-made/leave/balances-grid (year?: integer, businessLocationId?: string) -> `{ year, unit, types, rows, people }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/reports/{kind}":{"get":{"operationId":"LeaveController_report","summary":"Leave report rows","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"kind","required":true,"in":"path","schema":{"type":"string","enum":["balances","usage","pending","liability"]},"example":"liability"},{"name":"year","required":false,"in":"query","schema":{"type":"integer"},"example":2026},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"harbor-grill"}],"responses":{"200":{"description":"`{ columns:[[key,label]], rows, totals, count }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Rows for one report — `balances`, `usage`, `pending` or `liability` (anything else falls back to `balances`) — with column definitions and footer totals of the amount columns. Sites read as their names.\n\n#### Signature\n\n```http\nGET /business-made/leave/reports/{kind} (kind: string, year?: integer, businessLocationId?: string) -> `{ columns:[[key,label]], rows, totals, count }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/digest/run":{"post":{"operationId":"LeaveController_digestRun","summary":"Send the approvers' digest now","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ sent, waiting }` or `{ sent: 0, skipped: \"digest off\" }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"One email per approver listing everything waiting on them. Also runs nightly. Sends nothing when the org turned the digest off (`approval.dailyDigest: false`).\n\n#### Signature\n\n```http\nPOST /business-made/leave/digest/run () -> `{ sent, waiting }` or `{ sent: 0, skipped: \"digest off\" }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/taken/run":{"post":{"operationId":"LeaveController_takenRun","summary":"Mark past approved leave as taken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ date, taken }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Approved leave whose last day has passed becomes `taken`. Balances already moved at approval. Also runs nightly.\n\n#### Signature\n\n```http\nPOST /business-made/leave/taken/run () -> `{ date, taken }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/engine/status":{"get":{"operationId":"LeaveController_engineStatus","summary":"Leave scheduled jobs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ serverTime, jobs:[{name, cron, timezone, enabled, scheduled, nextRun, lastRun, warning?}], settings, escalation }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"What is scheduled for this org — each job's cron, timezone, whether it is enabled and queued, next and last run. The jobs are configured under `leave.jobs` in the readiness settings.\n\n#### Signature\n\n```http\nGET /business-made/leave/engine/status () -> `{ serverTime, jobs:[{name, cron, timezone, enabled, scheduled, nextRun, lastRun, warning?}], settings, escalation }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/readiness/settings`"}},"/business-made/leave/accrual/status":{"get":{"operationId":"LeaveController_accrualStatus","summary":"Accrual status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ year, balances, periodsPosted, lastRun, nextRun }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"This year's balances count, which months have been accrued, and the last run.\n\n#### Signature\n\n```http\nGET /business-made/leave/accrual/status () -> `{ year, balances, periodsPosted, lastRun, nextRun }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/accrual/run":{"post":{"operationId":"LeaveController_accrualRun","summary":"Run accrual now","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ period, posted, created, unchanged, rows }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Brings every accrued balance up to what its policy says has been earned by `asOf` (default today). Fixed types are set once. **Idempotent per month**: one accrual transaction per balance per month; a month already posted is skipped.\n\n#### Signature\n\n```http\nPOST /business-made/leave/accrual/run (body) -> `{ period, posted, created, unchanged, rows }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"asOf":{"type":"string","format":"date"}}},"example":{"asOf":"2026-09-30"}}}}}},"/business-made/leave/accrual/rollover":{"post":{"operationId":"LeaveController_accrualRollover","summary":"Year-end rollover","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ fromYear, toYear, created, skipped, carried }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Opens next year's balances from each policy, carrying over what the policy allows from `fromYear` (default this year). A next-year balance that already exists is never touched.\n\n#### Signature\n\n```http\nPOST /business-made/leave/accrual/rollover (body) -> `{ fromYear, toYear, created, skipped, carried }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"fromYear":{"type":"integer"}}},"example":{"fromYear":2026}}}}}},"/business-made/leave/calendar":{"get":{"operationId":"LeaveController_listDays","summary":"List calendar days","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":false,"in":"query","schema":{"type":"integer"},"example":2026},{"name":"location","required":false,"in":"query","schema":{"type":"string"},"description":"Location slug.","example":"harbor-grill"}],"responses":{"200":{"description":"Calendar days","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Public holidays, closures and special days (bm_calendar_day) for a year, sorted by date — optionally for one location.\n\n#### Signature\n\n```http\nGET /business-made/leave/calendar (year?: integer, location?: string) -> Calendar days\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"post":{"operationId":"LeaveController_saveDay","summary":"Create or update a calendar day","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved day","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A date and a name are required — `date` or `title` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A date and a name are required","path":"/business-made/leave/calendar","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Saves a holiday, closure or special day. With `sk` it updates that day; otherwise creates one. `kind` defaults to `holiday`, `observed` and `paid` to true; `hours` applies to a `special` day.\n\n#### Signature\n\n```http\nPOST /business-made/leave/calendar (body) -> The saved day\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DATE_TITLE_REQUIRED | A date and a name are required | `date` or `title` is missing. | Send both. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","title"],"properties":{"sk":{"type":"string"},"date":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"title":{"type":"string"},"kind":{"type":"string","enum":["holiday","closure","special"]},"location":{"type":"string"},"observed":{"type":"boolean"},"paid":{"type":"boolean"},"hours":{"type":"number"},"note":{"type":"string"}}},"example":{"date":"2026-12-25","title":"Christmas Day","kind":"holiday","paid":true}}}}}},"/business-made/leave/calendar/{id}":{"delete":{"operationId":"LeaveController_deleteDay","summary":"Delete a calendar day","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_calendar_day sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Removes a holiday, closure or special day.\n\n#### Signature\n\n```http\nDELETE /business-made/leave/calendar/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/leave/types":{"get":{"operationId":"LeaveController_getLeaveTypes","summary":"List leave types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Leave types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Every leave type defined for the org — annual, sick, parental and so on.\n\n#### Signature\n\n```http\nGET /business-made/leave/types () -> Leave types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/types/active`"},"post":{"operationId":"LeaveController_createLeaveType","summary":"Create a leave type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created leave type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Defines a leave type. Its accrual rules are what the balance endpoints apply, so get them right before employees start accruing against it.\n\n#### Signature\n\n```http\nPOST /business-made/leave/types (body) -> The created leave type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/balances`","requestBody":{"description":"The leave type to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"ANNUAL","name":"Annual leave","accrualRate":2.08,"unit":"days","maxCarryOver":5}}}}}},"/business-made/leave/types/active":{"get":{"operationId":"LeaveController_getActiveLeaveTypes","summary":"List active leave types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active leave types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Leave types currently available to request. Retired types stay on historical records but do not appear here.\n\n#### Signature\n\n```http\nGET /business-made/leave/types/active () -> Active leave types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests`"}},"/business-made/leave/types/{id}":{"get":{"operationId":"LeaveController_getLeaveType","summary":"Get a leave type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The leave type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Fetches one leave type with its accrual and carry-over rules.\n\n#### Signature\n\n```http\nGET /business-made/leave/types/{id} (id: string) -> The leave type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/types/update`"},"delete":{"operationId":"LeaveController_deleteLeaveType","summary":"Delete a leave type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Deletes a leave type. Balances and historical requests referencing it are not cleaned up — deactivate it instead where history matters.\n\n#### Signature\n\n```http\nDELETE /business-made/leave/types/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Orphans existing balances and requests.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/types/update`"}},"/business-made/leave/types/update":{"post":{"operationId":"LeaveController_updateLeaveType","summary":"Update a leave type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated leave type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Updates a leave type. Changing accrual rules affects **future** accruals only — balances already accrued are not recalculated. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/leave/types/update (body) -> The updated leave type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing balances are left as they are. Adjust them explicitly if a rule change should apply retrospectively.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/balances/{id}/adjust`","requestBody":{"description":"The leave type to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"LT-annual","data":{"maxCarryOver":10}}}}}}},"/business-made/leave/balances":{"get":{"operationId":"LeaveController_getLeaveBalances","summary":"List leave balances","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Leave balances","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Leave balances across the workforce — the liability figure for accrued but untaken leave.\n\n#### Signature\n\n```http\nGET /business-made/leave/balances () -> Leave balances\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/balances/employee/{employeeId}`"},"post":{"operationId":"LeaveController_createLeaveBalance","summary":"Create a leave balance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created balance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Opens a balance for an employee against a leave type — typically done at hire, or when a new leave type is introduced.\n\n#### Signature\n\n```http\nPOST /business-made/leave/balances (body) -> The created balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/balances/{id}/accrue`","requestBody":{"description":"The balance to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","leaveTypeId":"LT-annual","balance":25}}}}}},"/business-made/leave/balances/{id}":{"get":{"operationId":"LeaveController_getLeaveBalance","summary":"Get a leave balance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The balance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/leave/balances/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Fetches one balance record.\n\n#### Signature\n\n```http\nGET /business-made/leave/balances/{id} (id: string) -> The balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/balances/{id}/adjust`"}},"/business-made/leave/balances/employee/{employeeId}":{"get":{"operationId":"LeaveController_getEmployeeBalances","summary":"Get an employee's leave balances","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"year","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"The employee's balances","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"One employee's balances across every leave type — what a self-service leave screen shows.\n\n#### Signature\n\n```http\nGET /business-made/leave/balances/employee/{employeeId} (employeeId: string) -> The employee's balances\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests`"}},"/business-made/leave/balances/{id}/adjust":{"post":{"operationId":"LeaveController_adjustBalance","summary":"Adjust a leave balance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The adjusted balance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave balance not found — No balance record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave balance not found","path":"/business-made/leave/balances/{id}/adjust","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Applies a manual correction to a balance — a carry-over, a buy-back, or fixing an error. Adds `adjustment` to both the adjustment total and `available`, and appends an `adjustment` transaction carrying `reason` and who made it. Record a reason: an unexplained change to someone's leave entitlement is the kind of thing that gets disputed later.\n\n#### Signature\n\n```http\nPOST /business-made/leave/balances/{id}/adjust (id: string, body) -> The adjusted balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Signed — a negative amount reduces the balance.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BALANCE_NOT_FOUND | Leave balance not found | No balance record has that id. | The body carries `code` and the `balanceId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/balances/{id}/accrue`","requestBody":{"description":"The adjustment.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["adjustment","reason"],"properties":{"adjustment":{"type":"number","description":"Signed amount in the balance unit."},"reason":{"type":"string"}}},"example":{"adjustment":-2,"reason":"Correction — two days double-counted in the July accrual"}}}}}},"/business-made/leave/balances/{id}/accrue":{"post":{"operationId":"LeaveController_accrueLeave","summary":"Accrue leave on one balance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated balance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave balance not found — No balance record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave balance not found","path":"/business-made/leave/balances/{id}/accrue","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Adds `hours` to the balance's accrued and available amounts, appends an `accrual` transaction and stamps `lastAccrualDate`. **Not idempotent**: calling it twice accrues twice. For the policy-driven monthly run use POST /business-made/leave/accrual/run, which is.\n\n#### Signature\n\n```http\nPOST /business-made/leave/balances/{id}/accrue (id: string, body) -> The updated balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BALANCE_NOT_FOUND | Leave balance not found | No balance record has that id. | The body carries `code` and the `balanceId`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/accrual/run`","requestBody":{"description":"Accrual details.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["hours","accrualDate"],"properties":{"hours":{"type":"number"},"accrualDate":{"type":"string","format":"date"}}},"example":{"hours":6.67,"accrualDate":"2026-09-30"}}}}}},"/business-made/leave/requests":{"get":{"operationId":"LeaveController_getLeaveRequests","summary":"List leave requests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Leave requests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Leave requests across the org.\n\n#### Signature\n\n```http\nGET /business-made/leave/requests () -> Leave requests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/requests/pending-approvals`"},"post":{"operationId":"LeaveController_createLeaveRequest","summary":"Create a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Creates a leave request as a **draft**. It is not visible to approvers until submitted, so a half-filled request does not appear in the queue.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests (body) -> The created request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Creating does not submit. Call `submit` to send it for approval.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/submit`","requestBody":{"description":"The request.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","leaveTypeId":"LT-annual","startDate":"2026-10-05","endDate":"2026-10-09"}}}}}},"/business-made/leave/requests/pending-approvals":{"get":{"operationId":"LeaveController_getPendingApprovals","summary":"List requests awaiting approval","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"approver","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Pending requests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"The approval queue — submitted requests that nobody has decided on yet.\n\n#### Signature\n\n```http\nGET /business-made/leave/requests/pending-approvals () -> Pending requests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/approve`"}},"/business-made/leave/requests/{id}":{"get":{"operationId":"LeaveController_getLeaveRequest","summary":"Get a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/leave/requests/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Fetches one request with its dates, type and status.\n\n#### Signature\n\n```http\nGET /business-made/leave/requests/{id} (id: string) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/submit`"},"delete":{"operationId":"LeaveController_deleteLeaveRequest","summary":"Delete a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Deletes a request outright. It does not release a held balance or close its approval task — cancel instead, which does both and keeps the record of what was asked for.\n\n#### Signature\n\n```http\nDELETE /business-made/leave/requests/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/cancel`"}},"/business-made/leave/requests/update":{"post":{"operationId":"LeaveController_updateLeaveRequest","summary":"Update a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave request not found — No leave request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave request not found","path":"/business-made/leave/requests/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Updates a request. Editing one that has already been approved does not re-run approval — cancel and raise a new request instead. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests/update (body) -> The updated request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Leave request not found | No leave request has that id. | The body carries `code` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/cancel`","requestBody":{"description":"The request to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"LR-4821","data":{"endDate":"2026-10-10"}}}}}}},"/business-made/leave/requests/{id}/submit":{"post":{"operationId":"LeaveController_submitLeaveRequest","summary":"Submit a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The request, now `pending`, with `cost`, `approver` and `approvalChain`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A pending request cannot be submitted — The request is pending or approved already.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A pending request cannot be submitted","path":"/business-made/leave/requests/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave request not found — No leave request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave request not found","path":"/business-made/leave/requests/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Sends a draft (or a declined / cancelled one — a resubmission starts a new round) for approval. The server works out the cost in the org's unit (working days only — holidays and closures are skipped), resolves who decides (supervisor, site manager or HR by the approval flow; after a decline it goes back to whoever declined), **holds** the amount on the balance, opens a task on the `leave-approval` workflow and emails the approver and the employee. Blackout periods and short notice are shown to the approver, never blocked.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests/{id}/submit (id: string) -> The request, now `pending`, with `cost`, `approver` and `approvalChain`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Leave request not found | No leave request has that id. | The body carries `code` and the id. |\n| `400` | BAD_STATUS | A pending request cannot be submitted | The request is pending or approved already. | Nothing to do, or cancel it first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/approve`"}},"/business-made/leave/requests/{id}/edit":{"post":{"operationId":"LeaveController_editLeaveRequest","summary":"Edit a request before sending it (again)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The request after the edit","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A pending request cannot be edited — cancel it first — The request is not draft, rejected or cancelled (the status is named in the message).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A pending request cannot be edited — cancel it first","path":"/business-made/leave/requests/{id}/edit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave request not found — No leave request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave request not found","path":"/business-made/leave/requests/{id}/edit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Changes a **draft, declined or cancelled** request — dates, type, reason, half day. Only the fields sent change. A pending or approved request is refused: cancel it first.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests/{id}/edit (id: string, body) -> The request after the edit\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Leave request not found | No leave request has that id. | The body carries `code` and the id. |\n| `400` | BAD_STATUS | A pending request cannot be edited — cancel it first | The request is not draft, rejected or cancelled (the status is named in the message). | Cancel it, then edit and resubmit. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/resubmit`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"leaveTypeId":{"type":"string","description":"bm_leave_type sk"},"reason":{"type":"string"},"isPartialDay":{"type":"boolean"}}},"example":{"startDate":"2026-10-06","endDate":"2026-10-09"}}}}}},"/business-made/leave/requests/{id}/resubmit":{"post":{"operationId":"LeaveController_resubmitLeaveRequest","summary":"Send a request again","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The request, pending again","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a declined request, or one withdrawn before a decision, can be sent again — make a new request instead — The request is pending or approved, or was cancelled after an approval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a declined request, or one withdrawn before a decision, can be sent again — make a new request instead","path":"/business-made/leave/requests/{id}/resubmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave request not found — No leave request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave request not found","path":"/business-made/leave/requests/{id}/resubmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Sends a **declined** request, one withdrawn before any decision, or a draft back for approval — the same request, next round, with a new approval task. Optional edits in the body are applied first (same fields as edit). The approver sees who declined it last time and why.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests/{id}/resubmit (id: string, body) -> The request, pending again\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Leave request not found | No leave request has that id. | The body carries `code` and the id. |\n| `400` | CANNOT_RESUBMIT | Only a declined request, or one withdrawn before a decision, can be sent again — make a new request instead | The request is pending or approved, or was cancelled after an approval. | Create a new request. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/edit`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"leaveTypeId":{"type":"string"},"reason":{"type":"string"},"isPartialDay":{"type":"boolean"}}},"example":{"reason":"Moved a day later — cover is arranged"}}}}}},"/business-made/leave/requests/{id}/approve":{"post":{"operationId":"LeaveController_approveLeaveRequest","summary":"Approve a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The request after the decision","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This request is approved, not waiting on a decision — The request is not pending.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This request is approved, not waiting on a decision","path":"/business-made/leave/requests/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave request not found — No leave request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave request not found","path":"/business-made/leave/requests/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Approves a pending request and moves its approval task. When the policy needs two levels, a first-level approval hands the request to HR for the final say instead of finishing it. The final approval uses the held amount from the balance and flags the person's shifts on those days for cover.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests/{id}/approve (id: string, body) -> The request after the decision\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Leave request not found | No leave request has that id. | The body carries `code` and the id. |\n| `400` | BAD_STATUS | This request is approved, not waiting on a decision | The request is not pending. | Nothing to decide. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/reject`","requestBody":{"description":"Optional note (`note` or `comments`).","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"},"comments":{"type":"string"}}},"example":{"note":"Approved — cover arranged"}}}}}},"/business-made/leave/requests/{id}/reject":{"post":{"operationId":"LeaveController_rejectLeaveRequest","summary":"Reject a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The request after the decision","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This request is approved, not waiting on a decision — The request is not pending.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This request is approved, not waiting on a decision","path":"/business-made/leave/requests/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave request not found — No leave request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave request not found","path":"/business-made/leave/requests/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Declines a pending request with a reason the employee sees; the held amount goes back to the balance. The person can edit and resubmit it — it then returns to whoever declined.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests/{id}/reject (id: string, body) -> The request after the decision\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Leave request not found | No leave request has that id. | The body carries `code` and the id. |\n| `400` | BAD_STATUS | This request is approved, not waiting on a decision | The request is not pending. | Nothing to decide. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/resubmit`","requestBody":{"description":"Why it was declined (`note` or `reason`). Required.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"},"note":{"type":"string"}}},"example":{"reason":"Two others already off that week"}}}}}},"/business-made/leave/requests/{id}/cancel":{"post":{"operationId":"LeaveController_cancelLeaveRequest","summary":"Cancel a leave request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This request is already cancelled — The request is already cancelled or rejected.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This request is already cancelled","path":"/business-made/leave/requests/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Leave request not found — No leave request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Leave request not found","path":"/business-made/leave/requests/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Withdraws a request and closes its approval task. A pending request releases its held amount; an approved one returns the used amount to the balance. The approver (pending) or the employee (approved, when someone else cancelled) is told.\n\n#### Signature\n\n```http\nPOST /business-made/leave/requests/{id}/cancel (id: string, body) -> The cancelled request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Leave request not found | No leave request has that id. | The body carries `code` and the id. |\n| `400` | BAD_STATUS | This request is already cancelled | The request is already cancelled or rejected. | Nothing to do. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/requests/{id}/resubmit`","requestBody":{"description":"Why it was cancelled.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Plans changed"}}}}}},"/business-made/leave/requests/employee/{employeeId}":{"get":{"operationId":"LeaveController_getEmployeeRequests","summary":"Get an employee's leave requests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The employee's requests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"One employee's leave history and pending requests.\n\n#### Signature\n\n```http\nGET /business-made/leave/requests/employee/{employeeId} (employeeId: string) -> The employee's requests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/balances/employee/{employeeId}`"}},"/business-made/leave/policies":{"get":{"operationId":"LeaveController_getLeavePolicies","summary":"List leave policies","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Leave policies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"The leave policies defined for the org — the rules governing entitlement and approval.\n\n#### Signature\n\n```http\nGET /business-made/leave/policies () -> Leave policies\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/policies/{id}`"},"post":{"operationId":"LeaveController_createLeavePolicy","summary":"Create a leave policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Defines a leave policy.\n\n#### Signature\n\n```http\nPOST /business-made/leave/policies (body) -> The created policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/policies/update`","requestBody":{"description":"The policy to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"UK standard","minNoticeDays":14,"maxConsecutiveDays":15}}}}}},"/business-made/leave/policies/{id}":{"get":{"operationId":"LeaveController_getLeavePolicy","summary":"Get a leave policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Fetches one leave policy.\n\n#### Signature\n\n```http\nGET /business-made/leave/policies/{id} (id: string) -> The policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/policies/update`"},"delete":{"operationId":"LeaveController_deleteLeavePolicy","summary":"Delete a leave policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Deletes a leave policy.\n\n#### Signature\n\n```http\nDELETE /business-made/leave/policies/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/policies`"}},"/business-made/leave/policies/update":{"post":{"operationId":"LeaveController_updateLeavePolicy","summary":"Update a leave policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Updates a leave policy. Applies to future requests; approved leave is unaffected. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/leave/policies/update (body) -> The updated policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/policies`","requestBody":{"description":"The policy to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"LP-uk","data":{"minNoticeDays":21}}}}}}},"/business-made/leave/metrics":{"get":{"operationId":"LeaveController_getLeaveMetrics","summary":"Get leave metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Leave metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Leave"],"description":"Aggregate leave figures — utilisation, outstanding liability and absence patterns.\n\n#### Signature\n\n```http\nGET /business-made/leave/metrics () -> Leave metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/balances`"}},"/business-made/performance/goals":{"get":{"operationId":"PerformanceController_getGoals","summary":"List goals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Goals","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Performance goals across the org.\n\n#### Signature\n\n```http\nGET /business-made/performance/goals () -> Goals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/performance/goals/employee/{employeeId}`"},"post":{"operationId":"PerformanceController_createGoal","summary":"Create a goal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created goal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Creates a performance goal as a draft. Activating it is what makes it count toward a review.\n\n#### Signature\n\n```http\nPOST /business-made/performance/goals (body) -> The created goal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals/{id}/activate`","requestBody":{"description":"The goal to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","title":"Ship the platform migration","targetDate":"2026-12-31","measure":"All services migrated with no customer downtime"}}}}}},"/business-made/performance/goals/{id}":{"get":{"operationId":"PerformanceController_getGoal","summary":"Get a goal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The goal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/performance/goals/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Fetches one goal with its progress history.\n\n#### Signature\n\n```http\nGET /business-made/performance/goals/{id} (id: string) -> The goal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals/{id}/progress`"},"delete":{"operationId":"PerformanceController_deleteGoal","summary":"Delete a goal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Deletes a goal. Cancel instead where the record of what was set matters.\n\n#### Signature\n\n```http\nDELETE /business-made/performance/goals/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals/{id}/cancel`"}},"/business-made/performance/goals/update":{"post":{"operationId":"PerformanceController_updateGoal","summary":"Update a goal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated goal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Updates a goal. Changing the target mid-period is worth a note — a goal quietly rewritten to match the outcome is not a goal. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/performance/goals/update (body) -> The updated goal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals/{id}/progress`","requestBody":{"description":"The goal to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"GOL-4821","data":{"targetDate":"2027-01-31"}}}}}}},"/business-made/performance/goals/{id}/activate":{"post":{"operationId":"PerformanceController_activateGoal","summary":"Activate a goal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The activated goal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/goals/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Goal not found — No goal has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Goal not found","path":"/business-made/performance/goals/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Makes a goal live and countable toward the review period.\n\n#### Signature\n\n```http\nPOST /business-made/performance/goals/{id}/activate (id: string) -> The activated goal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | GOAL_NOT_FOUND | Goal not found | No goal has that id. | The body carries `code: \"GOAL_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals/{id}/progress`"}},"/business-made/performance/goals/{id}/progress":{"post":{"operationId":"PerformanceController_updateGoalProgress","summary":"Record goal progress","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated goal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/goals/{id}/progress","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Goal not found — No goal has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Goal not found","path":"/business-made/performance/goals/{id}/progress","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Records progress against a goal. Regular entries are what make a review evidence-based rather than a recollection of the last month.\n\n#### Signature\n\n```http\nPOST /business-made/performance/goals/{id}/progress (id: string, body) -> The updated goal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | GOAL_NOT_FOUND | Goal not found | No goal has that id. | The body carries `code: \"GOAL_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals/{id}/complete`","requestBody":{"description":"The progress update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"percentComplete":60,"note":"Three of five services migrated"}}}}}},"/business-made/performance/goals/{id}/complete":{"post":{"operationId":"PerformanceController_completeGoal","summary":"Complete a goal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The completed goal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/goals/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Goal not found — No goal has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Goal not found","path":"/business-made/performance/goals/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Marks a goal achieved, closing it for the review.\n\n#### Signature\n\n```http\nPOST /business-made/performance/goals/{id}/complete (id: string, body) -> The completed goal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | GOAL_NOT_FOUND | Goal not found | No goal has that id. | The body carries `code: \"GOAL_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals/{id}/cancel`","requestBody":{"description":"Optional closing note.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Completed two weeks early"}}}}}},"/business-made/performance/goals/{id}/cancel":{"post":{"operationId":"PerformanceController_cancelGoal","summary":"Cancel a goal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled goal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/goals/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Goal not found — No goal has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Goal not found","path":"/business-made/performance/goals/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Closes a goal that is no longer relevant — priorities changed, the project was cancelled. Keeping it with a reason is fairer at review time than deleting it.\n\n#### Signature\n\n```http\nPOST /business-made/performance/goals/{id}/cancel (id: string, body) -> The cancelled goal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | GOAL_NOT_FOUND | Goal not found | No goal has that id. | The body carries `code: \"GOAL_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/performance/goals/{id}`","requestBody":{"description":"Why it was cancelled.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Project deprioritised in Q4 planning"}}}}}},"/business-made/performance/goals/employee/{employeeId}":{"get":{"operationId":"PerformanceController_getEmployeeGoals","summary":"Get an employee's goals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The employee's goals","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"One employee's goals and how far along each is.\n\n#### Signature\n\n```http\nGET /business-made/performance/goals/employee/{employeeId} (employeeId: string) -> The employee's goals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/goals`"}},"/business-made/performance/reviews":{"get":{"operationId":"PerformanceController_getReviews","summary":"List performance reviews","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Reviews","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Reviews across the org at any stage.\n\n#### Signature\n\n```http\nGET /business-made/performance/reviews () -> Reviews\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Review content is sensitive — restrict to HR and the relevant management line.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/performance/reviews/employee/{employeeId}`"},"post":{"operationId":"PerformanceController_createReview","summary":"Create a performance review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Opens a review for an employee and period. It moves through self-assessment, manager review, completion and acknowledgement.\n\n#### Signature\n\n```http\nPOST /business-made/performance/reviews (body) -> The created review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/reviews/{id}/start-self-review`","requestBody":{"description":"The review to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","period":"2026-H2","reviewerId":"EMP-4001"}}}}}},"/business-made/performance/reviews/{id}":{"get":{"operationId":"PerformanceController_getReview","summary":"Get a performance review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/performance/reviews/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Fetches one review with its self-assessment, manager assessment and outcome.\n\n#### Signature\n\n```http\nGET /business-made/performance/reviews/{id} (id: string) -> The review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/reviews/{id}/complete`"},"delete":{"operationId":"PerformanceController_deleteReview","summary":"Delete a performance review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Deletes a review and its assessments. A completed review is part of someone's employment record — deleting one removes evidence that may be needed later.\n\n#### Signature\n\n```http\nDELETE /business-made/performance/reviews/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/performance/reviews/employee/{employeeId}`"}},"/business-made/performance/reviews/update":{"post":{"operationId":"PerformanceController_updateReview","summary":"Update a performance review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Updates a review's own fields. The assessment steps have their own endpoints so authorship is recorded. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/performance/reviews/update (body) -> The updated review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/reviews/{id}/manager-review`","requestBody":{"description":"The review to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"REV-4821","data":{"reviewerId":"EMP-4002"}}}}}}},"/business-made/performance/reviews/{id}/start-self-review":{"post":{"operationId":"PerformanceController_startSelfReview","summary":"Start the self-review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/reviews/{id}/start-self-review","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Review not found — No review has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Review not found","path":"/business-made/performance/reviews/{id}/start-self-review","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Opens the self-assessment stage, inviting the employee to write their own account before the manager's.\n\n#### Signature\n\n```http\nPOST /business-made/performance/reviews/{id}/start-self-review (id: string) -> The updated review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REVIEW_NOT_FOUND | Review not found | No review has that id. | The body carries `code: \"REVIEW_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/reviews/{id}/self-assessment`"}},"/business-made/performance/reviews/{id}/self-assessment":{"post":{"operationId":"PerformanceController_submitSelfAssessment","summary":"Submit a self-assessment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/reviews/{id}/self-assessment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Review not found — No review has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Review not found","path":"/business-made/performance/reviews/{id}/self-assessment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Records the employee's own assessment. Captured separately from the manager's so both perspectives sit in the record.\n\n#### Signature\n\n```http\nPOST /business-made/performance/reviews/{id}/self-assessment (id: string, body) -> The updated review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REVIEW_NOT_FOUND | Review not found | No review has that id. | The body carries `code: \"REVIEW_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/reviews/{id}/manager-review`","requestBody":{"description":"The self-assessment.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"summary":"Delivered the migration; want more scope on architecture","ratings":{"delivery":4,"collaboration":5}}}}}}},"/business-made/performance/reviews/{id}/manager-review":{"post":{"operationId":"PerformanceController_submitManagerReview","summary":"Submit a manager review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/reviews/{id}/manager-review","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Review not found — No review has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Review not found","path":"/business-made/performance/reviews/{id}/manager-review","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Records the manager's assessment. Kept distinct from the self-assessment so a disagreement between the two remains visible.\n\n#### Signature\n\n```http\nPOST /business-made/performance/reviews/{id}/manager-review (id: string, body) -> The updated review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REVIEW_NOT_FOUND | Review not found | No review has that id. | The body carries `code: \"REVIEW_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/reviews/{id}/complete`","requestBody":{"description":"The manager's assessment.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"summary":"Strong delivery, ready for more architectural ownership","ratings":{"delivery":4,"collaboration":4}}}}}}},"/business-made/performance/reviews/{id}/complete":{"post":{"operationId":"PerformanceController_completeReview","summary":"Complete a review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The completed review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/reviews/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Review not found — No review has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Review not found","path":"/business-made/performance/reviews/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Finalises the review. It still needs the employee's acknowledgement — completion is the manager finishing, not the employee having seen it.\n\n#### Signature\n\n```http\nPOST /business-made/performance/reviews/{id}/complete (id: string) -> The completed review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REVIEW_NOT_FOUND | Review not found | No review has that id. | The body carries `code: \"REVIEW_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/reviews/{id}/acknowledge`"}},"/business-made/performance/reviews/{id}/acknowledge":{"post":{"operationId":"PerformanceController_acknowledgeReview","summary":"Acknowledge a review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The acknowledged review","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/reviews/{id}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Review not found — No review has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Review not found","path":"/business-made/performance/reviews/{id}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Records that the employee has seen the completed review.\n\nAcknowledgement means *seen*, not *agreed*. It is the step that matters if a review is ever relied on in a formal process, because it evidences the employee was shown it.\n\n#### Signature\n\n```http\nPOST /business-made/performance/reviews/{id}/acknowledge (id: string, body) -> The acknowledged review\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REVIEW_NOT_FOUND | Review not found | No review has that id. | The body carries `code: \"REVIEW_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips`","requestBody":{"description":"Optional employee comment.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"comment":"Agree on delivery; would like the architecture scope defined concretely"}}}}}},"/business-made/performance/reviews/employee/{employeeId}":{"get":{"operationId":"PerformanceController_getEmployeeReviews","summary":"Get an employee's reviews","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The employee's reviews","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"One employee's review history.\n\n#### Signature\n\n```http\nGET /business-made/performance/reviews/employee/{employeeId} (employeeId: string) -> The employee's reviews\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/performance/reviews`"}},"/business-made/performance/pips":{"get":{"operationId":"PerformanceController_getPIPs","summary":"List performance improvement plans","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"PIPs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"PIPs across the org. Among the most sensitive records the platform holds — restrict access tightly.\n\n#### Signature\n\n```http\nGET /business-made/performance/pips () -> PIPs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A PIP is often a precursor to dismissal. Access should be narrow and auditable.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/performance/pips/{id}`"},"post":{"operationId":"PerformanceController_createPIP","summary":"Create a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Creates a performance improvement plan as a draft.\n\nA PIP is frequently the documented basis for a later dismissal, so the objectives should be specific and measurable and the review dates real. Vague objectives make the plan indefensible.\n\n#### Signature\n\n```http\nPOST /business-made/performance/pips (body) -> The created PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Specific, measurable objectives with real review dates — this record may be scrutinised.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/activate`","requestBody":{"description":"The plan to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","startDate":"2026-10-01","endDate":"2026-12-31","objectives":[{"objective":"Close assigned tickets within SLA","measure":"90% within SLA over the period"}]}}}}}},"/business-made/performance/pips/{id}":{"get":{"operationId":"PerformanceController_getPIP","summary":"Get a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/performance/pips/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Fetches one improvement plan with its objectives and check-in history.\n\n#### Signature\n\n```http\nGET /business-made/performance/pips/{id} (id: string) -> The PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/check-in`"},"delete":{"operationId":"PerformanceController_deletePIP","summary":"Delete a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Deletes an improvement plan. If it was ever active, deleting it destroys the record of a formal process — complete it instead.\n\n#### Signature\n\n```http\nDELETE /business-made/performance/pips/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/complete`"}},"/business-made/performance/pips/update":{"post":{"operationId":"PerformanceController_updatePIP","summary":"Update a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Updates a plan. Changing objectives after it has started should be recorded and explained — moving the bar mid-plan undermines it. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/performance/pips/update (body) -> The updated PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/extend`","requestBody":{"description":"The plan to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"PIP-4821","data":{"endDate":"2027-01-31"}}}}}}},"/business-made/performance/pips/{id}/activate":{"post":{"operationId":"PerformanceController_activatePIP","summary":"Activate a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The activated PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/pips/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"PIP not found — No PIP has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"PIP not found","path":"/business-made/performance/pips/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Starts the improvement plan. The employee should be told at this point — an active PIP they do not know about serves no purpose and helps nobody.\n\n#### Signature\n\n```http\nPOST /business-made/performance/pips/{id}/activate (id: string) -> The activated PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIP_NOT_FOUND | PIP not found | No PIP has that id. | The body carries `code: \"PIP_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/check-in`"}},"/business-made/performance/pips/{id}/check-in":{"post":{"operationId":"PerformanceController_addPIPCheckIn","summary":"Record a PIP check-in","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"PIP not found — No PIP has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"PIP not found","path":"/business-made/performance/pips/{id}/check-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Records a review meeting during the plan — progress against each objective and what was discussed. Regular check-ins are what make the process fair and the outcome defensible.\n\n#### Signature\n\n```http\nPOST /business-made/performance/pips/{id}/check-in (id: string, body) -> The updated PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIP_NOT_FOUND | PIP not found | No PIP has that id. | The body carries `code: \"PIP_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/complete`","requestBody":{"description":"The check-in.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"date":"2026-11-01","progress":"SLA compliance improved to 82%","notes":"On track; discussed ticket triage approach"}}}}}},"/business-made/performance/pips/{id}/complete":{"post":{"operationId":"PerformanceController_completePIP","summary":"Complete a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The completed PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/pips/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"PIP not found — No PIP has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"PIP not found","path":"/business-made/performance/pips/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Closes an active or extended plan (status becomes `completed-successful` or `completed-unsuccessful`) with its `result` and a `summary`, plus an optional `nextAction`. This is the conclusion any subsequent decision rests on, so state it plainly.\n\n#### Signature\n\n```http\nPOST /business-made/performance/pips/{id}/complete (id: string, body) -> The completed PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIP_NOT_FOUND | PIP not found | No PIP has that id. | The body carries `code: \"PIP_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/terminate`","requestBody":{"description":"The outcome.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["result","summary"],"properties":{"result":{"type":"string","enum":["successful","unsuccessful"]},"summary":{"type":"string"},"nextAction":{"type":"string"}}},"example":{"result":"successful","summary":"Sustained SLA compliance above 90% for the final six weeks"}}}}}},"/business-made/performance/pips/{id}/extend":{"post":{"operationId":"PerformanceController_extendPIP","summary":"Extend a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The extended PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/pips/{id}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"PIP not found — No PIP has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"PIP not found","path":"/business-made/performance/pips/{id}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Extends the plan period. Record why — an extension is either a genuine second chance or a delayed decision, and the record should show which.\n\n#### Signature\n\n```http\nPOST /business-made/performance/pips/{id}/extend (id: string, body) -> The extended PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIP_NOT_FOUND | PIP not found | No PIP has that id. | The body carries `code: \"PIP_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/complete`","requestBody":{"description":"The extension.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"newEndDate":"2027-01-31","reason":"Objectives partially met; agreed further period"}}}}}},"/business-made/performance/pips/{id}/cancel":{"post":{"operationId":"PerformanceController_cancelPIP","summary":"Cancel a PIP","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled PIP","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot complete this plan while it is draft — The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot complete this plan while it is draft","path":"/business-made/performance/pips/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"PIP not found — No PIP has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"PIP not found","path":"/business-made/performance/pips/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Cancels a draft, active or extended plan, recording the optional reason, who and when (`cancellation`).\n\n#### Signature\n\n```http\nPOST /business-made/performance/pips/{id}/cancel (id: string, body) -> The cancelled PIP\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PIP_NOT_FOUND | PIP not found | No PIP has that id. | The body carries `code: \"PIP_NOT_FOUND\"` and the id. |\n| `400` | wrong-state | Cannot complete this plan while it is draft | The record is not in a state that allows this step; the message names the step and the current state. The body carries `reason: \"wrong-state\"`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/performance/pips/{id}/complete`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Employee moved to a different role"}}}}}},"/business-made/performance/metrics":{"get":{"operationId":"PerformanceController_getPerformanceMetrics","summary":"Get performance metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Performance metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Performance"],"description":"Aggregate performance figures — goal completion, review coverage and rating distribution.\n\n#### Signature\n\n```http\nGET /business-made/performance/metrics () -> Performance metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/performance/reviews`"}},"/business-made/learning/courses":{"get":{"operationId":"LearningController_getCourses","summary":"List courses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Courses","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Every course defined for the org, published or not.\n\n#### Signature\n\n```http\nGET /business-made/learning/courses () -> Courses\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/learning/courses/status/active`"},"post":{"operationId":"LearningController_createCourse","summary":"Create a course","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created course","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Creates a course as a draft. Where it grants a certification with an expiry, that is what drives later renewal reminders.\n\n#### Signature\n\n```http\nPOST /business-made/learning/courses (body) -> The created course\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/courses/{id}/publish`","requestBody":{"description":"The course to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"FIRE-SAFETY","title":"Fire safety","durationMinutes":45,"certificationId":"CRT-fire","passMark":80}}}}}},"/business-made/learning/courses/{id}":{"get":{"operationId":"LearningController_getCourse","summary":"Get a course","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The course","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/learning/courses/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Fetches one course with its content and any certification it leads to.\n\n#### Signature\n\n```http\nGET /business-made/learning/courses/{id} (id: string) -> The course\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/courses/{id}/assign`"},"delete":{"operationId":"LearningController_deleteCourse","summary":"Delete a course","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Deletes a course. Enrolments and completion records referencing it are not removed — archive it instead where the training record matters.\n\n#### Signature\n\n```http\nDELETE /business-made/learning/courses/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/courses/{id}/archive`"}},"/business-made/learning/courses/update":{"post":{"operationId":"LearningController_updateCourse","summary":"Update a course","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated course","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Updates a course. Changing content after people have completed it does not invalidate their completions — version the course instead if the change is material. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/learning/courses/update (body) -> The updated course\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing completions stand against the old content.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/courses/{id}/archive`","requestBody":{"description":"The course to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"CRS-fire","data":{"durationMinutes":60}}}}}}},"/business-made/learning/courses/{id}/publish":{"post":{"operationId":"LearningController_publishCourse","summary":"Publish a course","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The published course","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/learning/courses/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Makes a course available to enrol in.\n\n#### Signature\n\n```http\nPOST /business-made/learning/courses/{id}/publish (id: string) -> The published course\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No course has that id. | The body carries `code: \"COURSE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/courses/{id}/assign`"}},"/business-made/learning/courses/{id}/archive":{"post":{"operationId":"LearningController_archiveCourse","summary":"Archive a course","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The archived course","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/learning/courses/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Retires a course while keeping it and its completion history. The right way to withdraw training people have already taken.\n\n#### Signature\n\n```http\nPOST /business-made/learning/courses/{id}/archive (id: string) -> The archived course\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No course has that id. | The body carries `code: \"COURSE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/learning/courses/{id}`"}},"/business-made/learning/courses/status/active":{"get":{"operationId":"LearningController_getActiveCourses","summary":"List active courses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active courses","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Courses currently available to enrol in — what a learning catalogue should show.\n\n#### Signature\n\n```http\nGET /business-made/learning/courses/status/active () -> Active courses\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments`"}},"/business-made/learning/courses/{id}/assign":{"post":{"operationId":"LearningController_assignCourse","summary":"Assign a course","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The created enrolments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/learning/courses/{id}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Assigns a course to employees, creating enrolments for each. The bulk path for mandatory training.\n\nWhere the training is a compliance requirement, this is what creates the evidence trail that it was assigned — and the expired-training report is what proves it stayed current.\n\n#### Signature\n\n```http\nPOST /business-made/learning/courses/{id}/assign (id: string, body) -> The created enrolments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No course has that id. | The body carries `code: \"COURSE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/training/expired`","requestBody":{"description":"Who to assign it to.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeIds":["EMP-4821","EMP-4822"],"dueDate":"2026-11-30"}}}}}},"/business-made/learning/certifications":{"get":{"operationId":"LearningController_getCertifications","summary":"List certifications","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Certifications","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"The certifications the org tracks — what a course can grant and what may expire.\n\n#### Signature\n\n```http\nGET /business-made/learning/certifications () -> Certifications\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/learning/enrollments/employee/{employeeId}/certifications`"},"post":{"operationId":"LearningController_createCertification","summary":"Create a certification","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created certification","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Defines a certification. Its validity period is what makes a completion expire and reappear as a compliance gap.\n\n#### Signature\n\n```http\nPOST /business-made/learning/certifications (body) -> The created certification\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/training/expired`","requestBody":{"description":"The certification to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"CRT-fire","name":"Fire safety certification","validForMonths":12}}}}}},"/business-made/learning/certifications/{id}":{"get":{"operationId":"LearningController_getCertification","summary":"Get a certification","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The certification","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Fetches one certification with its validity period.\n\n#### Signature\n\n```http\nGET /business-made/learning/certifications/{id} (id: string) -> The certification\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/certifications/update`"},"delete":{"operationId":"LearningController_deleteCertification","summary":"Delete a certification","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Deletes a certification definition. Records of people holding it are not removed.\n\n#### Signature\n\n```http\nDELETE /business-made/learning/certifications/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/learning/certifications`"}},"/business-made/learning/certifications/update":{"post":{"operationId":"LearningController_updateCertification","summary":"Update a certification","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated certification","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Updates a certification. Changing the validity period affects how existing certifications are judged — shortening it can make current holders immediately non-compliant. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/learning/certifications/update (body) -> The updated certification\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Check who becomes non-compliant before shortening a validity period.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/training/expired`","requestBody":{"description":"The certification to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"CRT-fire","data":{"validForMonths":24}}}}}}},"/business-made/learning/paths":{"get":{"operationId":"LearningController_getLearningPaths","summary":"List learning paths","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Learning paths","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Learning paths — ordered sequences of courses, such as a role-specific curriculum.\n\n#### Signature\n\n```http\nGET /business-made/learning/paths () -> Learning paths\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/learning/paths/{id}`"},"post":{"operationId":"LearningController_createLearningPath","summary":"Create a learning path","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created path","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Creates a path as a draft — a curriculum assembled from courses, in order.\n\n#### Signature\n\n```http\nPOST /business-made/learning/paths (body) -> The created path\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/paths/{id}/publish`","requestBody":{"description":"The path to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"New engineer onboarding","courseIds":["CRS-security","CRS-platform","CRS-oncall"]}}}}}},"/business-made/learning/paths/{id}":{"get":{"operationId":"LearningController_getLearningPath","summary":"Get a learning path","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The path","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/learning/paths/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Fetches one path with its ordered courses.\n\n#### Signature\n\n```http\nGET /business-made/learning/paths/{id} (id: string) -> The path\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/paths/{id}/publish`"},"delete":{"operationId":"LearningController_deleteLearningPath","summary":"Delete a learning path","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Deletes a learning path. The courses within it are unaffected.\n\n#### Signature\n\n```http\nDELETE /business-made/learning/paths/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/learning/paths`"}},"/business-made/learning/paths/update":{"post":{"operationId":"LearningController_updateLearningPath","summary":"Update a learning path","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated path","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Updates a path. Adding a course does not retroactively enrol people already partway through it. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/learning/paths/update (body) -> The updated path\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/paths/{id}/publish`","requestBody":{"description":"The path to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"PTH-eng","data":{"courseIds":["CRS-security","CRS-platform","CRS-oncall","CRS-incident"]}}}}}}},"/business-made/learning/paths/{id}/publish":{"post":{"operationId":"LearningController_publishLearningPath","summary":"Publish a learning path","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The published path","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Learning path not found — No learning path has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Learning path not found","path":"/business-made/learning/paths/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Makes a path available to enrol in.\n\n#### Signature\n\n```http\nPOST /business-made/learning/paths/{id}/publish (id: string) -> The published path\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LEARNING_PATH_NOT_FOUND | Learning path not found | No learning path has that id. | The body carries `code: \"LEARNING_PATH_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments`"}},"/business-made/learning/enrollments":{"get":{"operationId":"LearningController_getEnrollments","summary":"List enrolments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Enrolments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Course enrolments across the org and their progress.\n\n#### Signature\n\n```http\nGET /business-made/learning/enrollments () -> Enrolments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/learning/enrollments/employee/{employeeId}`"},"post":{"operationId":"LearningController_createEnrollment","summary":"Create an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Enrols an employee in a course or path individually. Use the course assign endpoint for bulk assignment.\n\n#### Signature\n\n```http\nPOST /business-made/learning/enrollments (body) -> The created enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/courses/{id}/assign`","requestBody":{"description":"The enrolment to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","courseId":"CRS-fire","dueDate":"2026-11-30"}}}}}},"/business-made/learning/enrollments/{id}":{"get":{"operationId":"LearningController_getEnrollment","summary":"Get an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/learning/enrollments/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Fetches one enrolment with its progress and assessment results.\n\n#### Signature\n\n```http\nGET /business-made/learning/enrollments/{id} (id: string) -> The enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments/{id}/progress`"}},"/business-made/learning/enrollments/update":{"post":{"operationId":"LearningController_updateEnrollment","summary":"Update an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Updates an enrolment — extending a due date, for instance. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/learning/enrollments/update (body) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments/{id}/start`","requestBody":{"description":"The enrolment to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"ENR-4821","data":{"dueDate":"2026-12-15"}}}}}}},"/business-made/learning/enrollments/{id}/start":{"post":{"operationId":"LearningController_startCourse","summary":"Start an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The started enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrolment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/learning/enrollments/{id}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Records the learner beginning the course, which starts measuring time to completion.\n\n#### Signature\n\n```http\nPOST /business-made/learning/enrollments/{id}/start (id: string) -> The started enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrolment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments/{id}/progress`"}},"/business-made/learning/enrollments/{id}/progress":{"post":{"operationId":"LearningController_updateCourseProgress","summary":"Record enrolment progress","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrolment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/learning/enrollments/{id}/progress","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Records how far through a course a learner is — module completion, time spent, position in the material.\n\n#### Signature\n\n```http\nPOST /business-made/learning/enrollments/{id}/progress (id: string, body) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrolment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments/{id}/complete`","requestBody":{"description":"The progress update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"percentComplete":60,"lastModule":"module-3"}}}}}},"/business-made/learning/enrollments/{id}/complete":{"post":{"operationId":"LearningController_completeCourse","summary":"Complete an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The completed enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrolment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/learning/enrollments/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Marks a course completed and grants any certification it carries.\n\nCompletion **starts the certification's expiry clock**, so this is the date the expired-training report measures from later.\n\n#### Signature\n\n```http\nPOST /business-made/learning/enrollments/{id}/complete (id: string, body) -> The completed enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrolment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/training/expired`","requestBody":{"description":"Optional completion detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"completedDate":"2026-10-20","score":88}}}}}},"/business-made/learning/enrollments/{id}/withdraw":{"post":{"operationId":"LearningController_withdrawFromCourse","summary":"Withdraw from an enrolment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The withdrawn enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrolment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/learning/enrollments/{id}/withdraw","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Records a learner withdrawing before completing. For mandatory training this leaves the requirement unmet — it does not discharge it.\n\n#### Signature\n\n```http\nPOST /business-made/learning/enrollments/{id}/withdraw (id: string, body) -> The withdrawn enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrolment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments/{id}/complete`","requestBody":{"description":"Why they withdrew.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Role changed; no longer required"}}}}}},"/business-made/learning/enrollments/{id}/assessment":{"post":{"operationId":"LearningController_recordAssessmentAttempt","summary":"Record an assessment result","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated enrolment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Enrollment not found — No enrolment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Enrollment not found","path":"/business-made/learning/enrollments/{id}/assessment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Records the result of a course assessment. Where the course sets a pass mark, this is what determines whether completion grants the certification.\n\n#### Signature\n\n```http\nPOST /business-made/learning/enrollments/{id}/assessment (id: string, body) -> The updated enrolment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ENROLLMENT_NOT_FOUND | Enrollment not found | No enrolment has that id. | The body carries `code: \"ENROLLMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/learning/enrollments/{id}/complete`","requestBody":{"description":"The assessment result.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"score":88,"passed":true,"attemptNumber":1}}}}}},"/business-made/learning/enrollments/employee/{employeeId}":{"get":{"operationId":"LearningController_getEmployeeEnrollments","summary":"Get an employee's enrolments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The employee's enrolments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"One employee's learning — assigned, in progress and completed.\n\n#### Signature\n\n```http\nGET /business-made/learning/enrollments/employee/{employeeId} (employeeId: string) -> The employee's enrolments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/learning/enrollments/employee/{employeeId}/certifications`"}},"/business-made/learning/enrollments/employee/{employeeId}/certifications":{"get":{"operationId":"LearningController_getEmployeeCertifications","summary":"Get an employee's certifications","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The employee's certifications","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"The certifications an employee currently holds, with expiry dates. The read behind checking whether someone is qualified for a task.\n\n#### Signature\n\n```http\nGET /business-made/learning/enrollments/employee/{employeeId}/certifications (employeeId: string) -> The employee's certifications\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/training/expired`"}},"/business-made/learning/metrics":{"get":{"operationId":"LearningController_getLearningMetrics","summary":"Get learning metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Learning metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Learning"],"description":"Aggregate learning figures — completion rates, overdue training and certification coverage.\n\n#### Signature\n\n```http\nGET /business-made/learning/metrics () -> Learning metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/training/expired`"}},"/business-made/compensation/view/changes":{"get":{"operationId":"CompensationController_viewChanges","summary":"Compensation changes for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["all","draft","pending-approval","approved","processed","rejected","cancelled"]},"example":"pending-approval"}],"responses":{"200":{"description":"`{ rows, counts, summary:{waiting, readyToApply, drafts, headline} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Every change as a finished row (person, current and new pay labels, status), newest first, with counts by status and a summary: how many are waiting for approval, approved but not yet applied to pay, and drafts, plus a one-line headline. `status` filters the rows (`all` = no filter); counts always cover everything.\n\n#### Signature\n\n```http\nGET /business-made/compensation/view/changes (status?: string) -> `{ rows, counts, summary:{waiting, readyToApply, drafts, headline} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/view/changes/{id}`"}},"/business-made/compensation/view/changes/{id}":{"get":{"operationId":"CompensationController_viewChange","summary":"One compensation change for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The change detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Compensation change not found. — No compensation change has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Compensation change not found.","path":"/business-made/compensation/view/changes/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"The change's row plus reason, notes, department, what the person is paid today (`currentPayLabel`, `currentPaySource`), a timeline (drafted, sent, approved/declined, applied, cancelled) and, for a draft, `edit` — the values to prefill the form.\n\n#### Signature\n\n```http\nGET /business-made/compensation/view/changes/{id} (id: string) -> The change detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COMP_CHANGE_NOT_FOUND | Compensation change not found. | No compensation change has that id. | The body carries `code: \"COMP_CHANGE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/view/bonuses":{"get":{"operationId":"CompensationController_viewBonuses","summary":"Bonuses for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["all","draft","pending-approval","approved","scheduled","paid","rejected","cancelled"]},"example":"approved"}],"responses":{"200":{"description":"`{ rows, counts, summary:{waiting, owedLabel, paidThisYearLabel, year, headline} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Every bonus as a finished row, newest first, with counts by status and a summary: waiting for approval, the approved-not-yet-paid total (`owedLabel`) and what was paid this year. `status` filters the rows.\n\n#### Signature\n\n```http\nGET /business-made/compensation/view/bonuses (status?: string) -> `{ rows, counts, summary:{waiting, owedLabel, paidThisYearLabel, year, headline} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/view/bonuses/{id}":{"get":{"operationId":"CompensationController_viewBonus","summary":"One bonus for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The bonus detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found. — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found.","path":"/business-made/compensation/view/bonuses/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"The bonus row plus description, notes, a timeline (drafted, sent, approved/declined, paid, cancelled) and, for a draft, `edit` values.\n\n#### Signature\n\n```http\nGET /business-made/compensation/view/bonuses/{id} (id: string) -> The bonus detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found. | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/view/grades":{"get":{"operationId":"CompensationController_viewGrades","summary":"Salary grades for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["all","draft","active","inactive","archived"]},"example":"active"}],"responses":{"200":{"description":"`{ rows, counts }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Every grade as a finished row with counts by status. `status` filters the rows.\n\n#### Signature\n\n```http\nGET /business-made/compensation/view/grades (status?: string) -> `{ rows, counts }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/view/grades/{id}":{"get":{"operationId":"CompensationController_viewGrade","summary":"One salary grade for the HR screen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The grade detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Salary grade not found. — No salary grade has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Salary grade not found.","path":"/business-made/compensation/view/grades/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"The grade row plus its raw `data`.\n\n#### Signature\n\n```http\nGET /business-made/compensation/view/grades/{id} (id: string) -> The grade detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SALARY_GRADE_NOT_FOUND | Salary grade not found. | No salary grade has that id. | The body carries `code: \"SALARY_GRADE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/view/pay/{employeeId}":{"get":{"operationId":"CompensationController_viewPay","summary":"What a person is paid today","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"`{ employeeSk, name, title, payType, hourlyRate, baseSalary, label, source, … }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Person not found. — No person has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Person not found.","path":"/business-made/compensation/view/pay/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Shown on the new-change form before anything is typed: pay type, hourly rate or base salary, a label, and where it came from (payroll profile or employee record). `employeeId` may be the bm_employee sk or the employee code.\n\n#### Signature\n\n```http\nGET /business-made/compensation/view/pay/{employeeId} (employeeId: string) -> `{ employeeSk, name, title, payType, hourlyRate, baseSalary, label, source, … }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Person not found. | No person has that id. | The body carries `code: \"EMPLOYEE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/changes/{id}/cancel":{"post":{"operationId":"CompensationController_cancelChange","summary":"Cancel a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled change","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This change can no longer be cancelled. — The change is processed, declined or already cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This change can no longer be cancelled.","path":"/business-made/compensation/changes/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Compensation change not found. — No compensation change has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Compensation change not found.","path":"/business-made/compensation/changes/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Cancels a draft, pending, submitted or approved change (not one already processed), recording who and when.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/changes/{id}/cancel (id: string) -> The cancelled change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COMP_CHANGE_NOT_FOUND | Compensation change not found. | No compensation change has that id. | The body carries `code: \"COMP_CHANGE_NOT_FOUND\"` and the id. |\n| `400` | NOT_CANCELLABLE | This change can no longer be cancelled. | The change is processed, declined or already cancelled. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/bonuses/{id}/reject":{"post":{"operationId":"CompensationController_rejectBonus","summary":"Decline a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The declined bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a bonus waiting for approval can be declined. — The bonus is not pending approval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a bonus waiting for approval can be declined.","path":"/business-made/compensation/bonuses/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found. — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found.","path":"/business-made/compensation/bonuses/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Declines a bonus waiting for approval, recording who, when and the optional reason.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses/{id}/reject (id: string, body) -> The declined bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found. | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n| `400` | NOT_PENDING | Only a bonus waiting for approval can be declined. | The bonus is not pending approval. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Outside the bonus budget this quarter"}}}}}},"/business-made/compensation/bonuses/{id}/cancel":{"post":{"operationId":"CompensationController_cancelBonus","summary":"Cancel a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This bonus can no longer be cancelled. — The bonus is paid, declined or already cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This bonus can no longer be cancelled.","path":"/business-made/compensation/bonuses/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found. — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found.","path":"/business-made/compensation/bonuses/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Cancels a draft, pending, approved or scheduled bonus (not one already paid), recording who and when.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses/{id}/cancel (id: string) -> The cancelled bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found. | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n| `400` | NOT_CANCELLABLE | This bonus can no longer be cancelled. | The bonus is paid, declined or already cancelled. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/grades/{id}/deactivate":{"post":{"operationId":"CompensationController_deactivateGrade","summary":"Deactivate a salary grade","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated grade","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Salary grade not found. — No salary grade has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Salary grade not found.","path":"/business-made/compensation/grades/{id}/deactivate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Sets the grade `inactive` — kept on record, not offered for new assignments.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/grades/{id}/deactivate (id: string) -> The updated grade\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SALARY_GRADE_NOT_FOUND | Salary grade not found. | No salary grade has that id. | The body carries `code: \"SALARY_GRADE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/grades/{id}/archive":{"post":{"operationId":"CompensationController_archiveGrade","summary":"Archive a salary grade","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated grade","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Salary grade not found. — No salary grade has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Salary grade not found.","path":"/business-made/compensation/grades/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Sets the grade `archived`.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/grades/{id}/archive (id: string) -> The updated grade\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SALARY_GRADE_NOT_FOUND | Salary grade not found. | No salary grade has that id. | The body carries `code: \"SALARY_GRADE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/compensation/grades":{"get":{"operationId":"CompensationController_getSalaryGrades","summary":"List salary grades","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Salary grades","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"The salary bands defined for the org — the ranges compensation changes are checked against.\n\n#### Signature\n\n```http\nGET /business-made/compensation/grades () -> Salary grades\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/grades/code/{code}`"},"post":{"operationId":"CompensationController_createSalaryGrade","summary":"Create a salary grade","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created grade","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Give the grade a code, e.g. L1 or M2. — `code` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give the grade a code, e.g. L1 or M2.","path":"/business-made/compensation/grades","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Defines a salary band (bm_salary_grade), as a **draft** unless `status` is given. The code must be unique; set a salary range, an hourly range or both.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/grades (body) -> The created grade\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | GRADE_CODE_REQUIRED | Give the grade a code, e.g. L1 or M2. | `code` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/grades/{id}/activate`","requestBody":{"description":"The grade to create.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","title","level"],"properties":{"code":{"type":"string"},"title":{"type":"string"},"level":{"type":"number"},"salaryRange":{"type":"object","properties":{"minimum":{"type":"number"},"midpoint":{"type":"number"},"maximum":{"type":"number"}}},"hourlyRange":{"type":"object","properties":{"minimum":{"type":"number"},"maximum":{"type":"number"}}},"status":{"type":"string","enum":["draft","active","inactive","archived"]}}},"example":{"code":"G7","title":"Senior Engineer","level":7,"salaryRange":{"minimum":80000,"midpoint":92000,"maximum":104000}}}}}}},"/business-made/compensation/grades/{id}":{"get":{"operationId":"CompensationController_getSalaryGrade","summary":"Get a salary grade","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The grade, or null when no grade has that id","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/compensation/grades/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Fetches one salary grade record (bm_salary_grade) with its salary and/or hourly range.\n\n#### Signature\n\n```http\nGET /business-made/compensation/grades/{id} (id: string) -> The grade, or null when no grade has that id\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/view/grades/{id}`"},"delete":{"operationId":"CompensationController_deleteSalaryGrade","summary":"Delete a salary grade","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Deletes a grade. Positions and employees assigned to it are not reassigned.\n\n#### Signature\n\n```http\nDELETE /business-made/compensation/grades/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/grades`"}},"/business-made/compensation/grades/update":{"post":{"operationId":"CompensationController_updateSalaryGrade","summary":"Update a salary grade","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated grade","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Give the grade a code, e.g. L1 or M2. — `code` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give the grade a code, e.g. L1 or M2.","path":"/business-made/compensation/grades/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Salary grade not found — No salary grade has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Salary grade not found","path":"/business-made/compensation/grades/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Edits a grade (body is the record: `sk` plus `data`). The same checks as create run. Existing salaries are unaffected — moving a band does not move anyone within it.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/grades/update (body) -> The updated grade\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SALARY_GRADE_NOT_FOUND | Salary grade not found | No salary grade has that id. | The body carries `code: \"SALARY_GRADE_NOT_FOUND\"` and the id. |\n| `400` | GRADE_CODE_REQUIRED | Give the grade a code, e.g. L1 or M2. | `code` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/view/grades`","requestBody":{"description":"The grade to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f0c3a1e4b0a1b2c3d4e5f6","data":{"code":"G7","title":"Senior Engineer","level":7,"salaryRange":{"minimum":82000,"maximum":106000}}}}}}}},"/business-made/compensation/grades/{id}/activate":{"post":{"operationId":"CompensationController_activateSalaryGrade","summary":"Activate a salary grade","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The activated grade","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Salary grade not found — No salary grade has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Salary grade not found","path":"/business-made/compensation/grades/{id}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Brings a grade into use so it can be assigned. Grades can be drafted before a pay review and activated when the new structure takes effect.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/grades/{id}/activate (id: string) -> The activated grade\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SALARY_GRADE_NOT_FOUND | Salary grade not found | No salary grade has that id. | The body carries `code: \"SALARY_GRADE_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/grades`"}},"/business-made/compensation/grades/code/{code}":{"get":{"operationId":"CompensationController_getSalaryGradeByCode","summary":"Get a salary grade by code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"path","schema":{"type":"string"},"description":"Grade code.","example":"G7"}],"responses":{"200":{"description":"The grade, or null when no grade has that code","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Resolves a grade from its business code rather than its record id.\n\n#### Signature\n\n```http\nGET /business-made/compensation/grades/code/{code} (code: string) -> The grade, or null when no grade has that code\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/grades/{id}`"}},"/business-made/compensation/changes":{"get":{"operationId":"CompensationController_getCompensationChanges","summary":"List compensation changes","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Compensation changes","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Pay changes across the org, at any stage of the approval lifecycle.\n\n#### Signature\n\n```http\nGET /business-made/compensation/changes () -> Compensation changes\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/changes/employee/{employeeId}`"},"post":{"operationId":"CompensationController_createCompensationChange","summary":"Create a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The draft change","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick the person this change is for. — Neither `employeeSk` nor `employeeId` names a person.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick the person this change is for.","path":"/business-made/compensation/changes","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Proposes a pay change as a **draft**. The server fills in the person, their current pay (`previousCompensation`, read from payroll while the change is open), the change amount and percentage, and the fiscal year. It does not affect pay until submitted, approved and processed — three deliberate steps between proposing a raise and someone being paid it.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/changes (body) -> The draft change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Creating changes nothing. Payroll only sees it after `process`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMPLOYEE_REQUIRED | Pick the person this change is for. | Neither `employeeSk` nor `employeeId` names a person. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/changes/{id}/submit`","requestBody":{"description":"The proposed change.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"employeeSk":{"type":"string","description":"bm_employee sk (or send employeeId, the employee code)."},"employeeId":{"type":"string"},"changeType":{"type":"string","example":"merit"},"effectiveDate":{"type":"string","format":"date"},"newCompensation":{"type":"object","properties":{"payType":{"type":"string","enum":["hourly","salary"]},"hourlyRate":{"type":"number"},"baseSalary":{"type":"number"},"payFrequency":{"type":"string"}}},"reason":{"type":"string"},"notes":{"type":"string"}}},"example":{"employeeId":"EMP-4821","changeType":"promotion","effectiveDate":"2026-10-01","newCompensation":{"payType":"salary","baseSalary":88000},"reason":"Promotion to Senior Engineer"}}}}}},"/business-made/compensation/changes/{id}":{"get":{"operationId":"CompensationController_getCompensationChange","summary":"Get a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The change, or null when none has that id","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/compensation/changes/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Fetches one pay change record with its previous and new compensation and approval state.\n\n#### Signature\n\n```http\nGET /business-made/compensation/changes/{id} (id: string) -> The change, or null when none has that id\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/view/changes/{id}`"},"delete":{"operationId":"CompensationController_deleteCompensationChange","summary":"Delete a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This change is part of someone's pay history — cancel it instead of deleting it. — The change is pending approval, approved or processed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This change is part of someone's pay history — cancel it instead of deleting it.","path":"/business-made/compensation/changes/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Deletes a draft, declined or cancelled change. One waiting for approval, approved or processed is part of someone's pay history and is refused — cancel it instead.\n\n#### Signature\n\n```http\nDELETE /business-made/compensation/changes/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | KEEP_HISTORY | This change is part of someone's pay history — cancel it instead of deleting it. | The change is pending approval, approved or processed. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/changes/{id}/cancel`"}},"/business-made/compensation/changes/update":{"post":{"operationId":"CompensationController_updateCompensationChange","summary":"Edit a draft compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated change","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a draft change can be edited — cancel it and raise a new one. — The change is past draft.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a draft change can be edited — cancel it and raise a new one.","path":"/business-made/compensation/changes/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Compensation change not found — No compensation change has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Compensation change not found","path":"/business-made/compensation/changes/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Edits a **draft** change (body: `sk` plus the fields, same as create); the derived fields are recomputed. Anything past draft is refused — cancel it and raise a new one.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/changes/update (body) -> The updated change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COMP_CHANGE_NOT_FOUND | Compensation change not found | No compensation change has that id. | The body carries `code: \"COMP_CHANGE_NOT_FOUND\"` and the id. |\n| `400` | NOT_DRAFT | Only a draft change can be edited — cancel it and raise a new one. | The change is past draft. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/changes/{id}/cancel`","requestBody":{"description":"The change to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f0c3a1e4b0a1b2c3d4e5f6","effectiveDate":"2026-11-01","newCompensation":{"baseSalary":90000}}}}}}},"/business-made/compensation/changes/{id}/submit":{"post":{"operationId":"CompensationController_submitForApproval","summary":"Submit a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The submitted change","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a draft change can be sent for approval. — The change is not a draft.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a draft change can be sent for approval.","path":"/business-made/compensation/changes/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Compensation change not found — No compensation change has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Compensation change not found","path":"/business-made/compensation/changes/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Sends a draft pay change for approval (`pending-approval`).\n\n#### Signature\n\n```http\nPOST /business-made/compensation/changes/{id}/submit (id: string) -> The submitted change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COMP_CHANGE_NOT_FOUND | Compensation change not found | No compensation change has that id. | The body carries `code: \"COMP_CHANGE_NOT_FOUND\"` and the id. |\n| `400` | NOT_DRAFT | Only a draft change can be sent for approval. | The change is not a draft. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/changes/{id}/approve`"}},"/business-made/compensation/changes/{id}/approve":{"post":{"operationId":"CompensationController_approveCompensationChange","summary":"Approve a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The approved change","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a change waiting for approval can be approved. — The change is not pending approval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a change waiting for approval can be approved.","path":"/business-made/compensation/changes/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Compensation change not found — No compensation change has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Compensation change not found","path":"/business-made/compensation/changes/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Approves a change waiting for approval. **Approval alone does not change anyone's pay** — the change must still be processed.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/changes/{id}/approve (id: string, body) -> The approved change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COMP_CHANGE_NOT_FOUND | Compensation change not found | No compensation change has that id. | The body carries `code: \"COMP_CHANGE_NOT_FOUND\"` and the id. |\n| `400` | NOT_PENDING | Only a change waiting for approval can be approved. | The change is not pending approval. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/changes/{id}/process`","requestBody":{"description":"Optional approval comments.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"comments":{"type":"string"}}},"example":{"comments":"Within the review budget"}}}}}},"/business-made/compensation/changes/{id}/reject":{"post":{"operationId":"CompensationController_rejectCompensationChange","summary":"Decline a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The declined change","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a change waiting for approval can be declined. — The change is not pending approval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a change waiting for approval can be declined.","path":"/business-made/compensation/changes/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Compensation change not found — No compensation change has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Compensation change not found","path":"/business-made/compensation/changes/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Declines a change waiting for approval, with a reason.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/changes/{id}/reject (id: string, body) -> The declined change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COMP_CHANGE_NOT_FOUND | Compensation change not found | No compensation change has that id. | The body carries `code: \"COMP_CHANGE_NOT_FOUND\"` and the id. |\n| `400` | NOT_PENDING | Only a change waiting for approval can be declined. | The change is not pending approval. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/changes`","requestBody":{"description":"Why it was declined.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Above band maximum for the grade"}}}}}},"/business-made/compensation/changes/{id}/process":{"post":{"operationId":"CompensationController_processCompensationChange","summary":"Process a compensation change","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The processed change, with `appliedTo`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Compensation change must be approved before processing — The change is not approved; the body carries its `status`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Compensation change must be approved before processing","path":"/business-made/compensation/changes/{id}/process","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Compensation change not found — No compensation change has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Compensation change not found","path":"/business-made/compensation/changes/{id}/process","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Applies an approved pay change: writes the new pay type and rate or salary (and pay frequency) onto the **employee record** and, when the person has one, their **payroll profile**; `appliedTo` records which. **This is the step that actually changes what someone is paid.**\n\nRecalculate any open payroll run afterwards — a run already calculated keeps the old rate until it is.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/changes/{id}/process (id: string) -> The processed change, with `appliedTo`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Recalculate any in-flight payroll run, or it will pay the previous salary.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COMP_CHANGE_NOT_FOUND | Compensation change not found | No compensation change has that id. | The body carries `code: \"COMP_CHANGE_NOT_FOUND\"` and the id. |\n| `400` | NOT_APPROVED | Compensation change must be approved before processing | The change is not approved; the body carries its `status`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{id}/calculate`"}},"/business-made/compensation/changes/employee/{employeeId}":{"get":{"operationId":"CompensationController_getEmployeeCompensationHistory","summary":"Get an employee's compensation history","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The employee's compensation changes","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Every pay change for one employee — their salary history and the reasons behind it.\n\n#### Signature\n\n```http\nGET /business-made/compensation/changes/employee/{employeeId} (employeeId: string) -> The employee's compensation changes\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Highly sensitive. Restrict to HR and the employee's own management line.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/changes`"}},"/business-made/compensation/bonuses":{"get":{"operationId":"CompensationController_getBonuses","summary":"List bonuses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Bonuses","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Bonuses across the org at any stage of their lifecycle.\n\n#### Signature\n\n```http\nGET /business-made/compensation/bonuses () -> Bonuses\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/bonuses/employee/{employeeId}`"},"post":{"operationId":"CompensationController_createBonus","summary":"Create a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The draft bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick the person this bonus is for. — Neither `employeeSk` nor `employeeId` names a person.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick the person this bonus is for.","path":"/business-made/compensation/bonuses","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Proposes a bonus as a **draft**; the server fills in the person and rounds the amount. It goes through submit → approve → schedule → paid before any money moves.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses (body) -> The draft bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMPLOYEE_REQUIRED | Pick the person this bonus is for. | Neither `employeeSk` nor `employeeId` names a person. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/bonuses/{id}/submit`","requestBody":{"description":"The bonus to propose.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"employeeSk":{"type":"string"},"employeeId":{"type":"string"},"bonusType":{"type":"string"},"title":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string","default":"USD"},"paymentDate":{"type":"string","format":"date"},"description":{"type":"string"}}},"example":{"employeeId":"EMP-4821","bonusType":"performance","title":"H1 performance bonus","amount":5000}}}}}},"/business-made/compensation/bonuses/{id}":{"get":{"operationId":"CompensationController_getBonus","summary":"Get a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The bonus, or null when none has that id","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/compensation/bonuses/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Fetches one bonus record with its amount and status.\n\n#### Signature\n\n```http\nGET /business-made/compensation/bonuses/{id} (id: string) -> The bonus, or null when none has that id\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/view/bonuses/{id}`"},"delete":{"operationId":"CompensationController_deleteBonus","summary":"Delete a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This bonus is part of someone's pay history — cancel it instead of deleting it. — The bonus is pending approval, approved, scheduled or paid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This bonus is part of someone's pay history — cancel it instead of deleting it.","path":"/business-made/compensation/bonuses/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Deletes a draft, declined or cancelled bonus. One pending approval, approved, scheduled or paid is part of someone's pay history and is refused — cancel it instead.\n\n#### Signature\n\n```http\nDELETE /business-made/compensation/bonuses/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | KEEP_HISTORY | This bonus is part of someone's pay history — cancel it instead of deleting it. | The bonus is pending approval, approved, scheduled or paid. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/bonuses/{id}/cancel`"}},"/business-made/compensation/bonuses/update":{"post":{"operationId":"CompensationController_updateBonus","summary":"Edit a draft bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a draft bonus can be edited — cancel it and raise a new one. — The bonus is past draft.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a draft bonus can be edited — cancel it and raise a new one.","path":"/business-made/compensation/bonuses/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found","path":"/business-made/compensation/bonuses/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Edits a **draft** bonus (body: `sk` plus the fields, same as create). Anything past draft is refused — cancel it and raise a new one.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses/update (body) -> The updated bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n| `400` | NOT_DRAFT | Only a draft bonus can be edited — cancel it and raise a new one. | The bonus is past draft. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/bonuses/{id}/cancel`","requestBody":{"description":"The bonus to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"66f0c3a1e4b0a1b2c3d4e5f6","amount":6000}}}}}},"/business-made/compensation/bonuses/{id}/submit":{"post":{"operationId":"CompensationController_submitBonusForApproval","summary":"Submit a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The submitted bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a draft bonus can be sent for approval. — The bonus is not a draft.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a draft bonus can be sent for approval.","path":"/business-made/compensation/bonuses/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found","path":"/business-made/compensation/bonuses/{id}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Sends a draft bonus for approval.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses/{id}/submit (id: string) -> The submitted bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n| `400` | NOT_DRAFT | Only a draft bonus can be sent for approval. | The bonus is not a draft. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/bonuses/{id}/approve`"}},"/business-made/compensation/bonuses/{id}/approve":{"post":{"operationId":"CompensationController_approveBonus","summary":"Approve a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The approved bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a bonus waiting for approval can be approved. — The bonus is not pending approval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a bonus waiting for approval can be approved.","path":"/business-made/compensation/bonuses/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found","path":"/business-made/compensation/bonuses/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Approves a bonus waiting for approval. It still needs scheduling before it reaches a payroll run.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses/{id}/approve (id: string) -> The approved bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n| `400` | NOT_PENDING | Only a bonus waiting for approval can be approved. | The bonus is not pending approval. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/bonuses/{id}/schedule`"}},"/business-made/compensation/bonuses/{id}/schedule":{"post":{"operationId":"CompensationController_scheduleBonus","summary":"Schedule a bonus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The scheduled bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only an approved bonus can be scheduled. — The bonus is not approved or scheduled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only an approved bonus can be scheduled.","path":"/business-made/compensation/bonuses/{id}/schedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found","path":"/business-made/compensation/bonuses/{id}/schedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Sets the date an approved (or already scheduled) bonus will be paid, putting it into the run that covers that period.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses/{id}/schedule (id: string, body) -> The scheduled bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n| `400` | NOT_APPROVED | Only an approved bonus can be scheduled. | The bonus is not approved or scheduled. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/compensation/bonuses/{id}/paid`","requestBody":{"description":"When to pay it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentDate"],"properties":{"paymentDate":{"type":"string","format":"date"},"payrollPeriod":{"type":"string"}}},"example":{"paymentDate":"2026-01-20"}}}}}},"/business-made/compensation/bonuses/{id}/paid":{"post":{"operationId":"CompensationController_markBonusPaid","summary":"Mark a bonus paid","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The paid bonus","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only an approved or scheduled bonus can be marked paid. — The bonus is not approved or scheduled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only an approved or scheduled bonus can be marked paid.","path":"/business-made/compensation/bonuses/{id}/paid","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bonus not found — No bonus has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bonus not found","path":"/business-made/compensation/bonuses/{id}/paid","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Records that an approved or scheduled bonus has been paid, closing it, optionally with the pay stub and payroll run that carried it.\n\n#### Signature\n\n```http\nPOST /business-made/compensation/bonuses/{id}/paid (id: string, body) -> The paid bonus\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BONUS_NOT_FOUND | Bonus not found | No bonus has that id. | The body carries `code: \"BONUS_NOT_FOUND\"` and the id. |\n| `400` | NOT_APPROVED | Only an approved or scheduled bonus can be marked paid. | The bonus is not approved or scheduled. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/view/bonuses`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"payStubId":{"type":"string"},"payrollRunId":{"type":"string"}}},"example":{"payrollRunId":"PR-2026-01"}}}}}},"/business-made/compensation/bonuses/employee/{employeeId}":{"get":{"operationId":"CompensationController_getEmployeeBonuses","summary":"Get an employee's bonuses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"fiscalYear","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Bonuses","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"One employee's bonus history.\n\n#### Signature\n\n```http\nGET /business-made/compensation/bonuses/employee/{employeeId} (employeeId: string) -> Bonuses\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/bonuses`"}},"/business-made/compensation/metrics":{"get":{"operationId":"CompensationController_getCompensationMetrics","summary":"Get compensation metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"fiscalYear","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Compensation metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Compensation"],"description":"Aggregate pay figures — distribution against bands, compa-ratios and where people sit outside their grade.\n\n#### Signature\n\n```http\nGET /business-made/compensation/metrics () -> Compensation metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/compensation/grades`"}},"/business-made/organization/chart":{"get":{"operationId":"OrganizationController_chart","summary":"Org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ people:[{id, name, departmentId, managerId, reports, reportsCount, chain, …}], departments, positions, unplaced, noDepartment, levels, ladderSet, totals:{people, departments, placed, unplaced, positions, openSeats} }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Everyone placed in departments with their manager, reports and reporting chain; departments and positions (with open seats); who has no department; people grouped by job level (when a level ladder is set in the leave settings); and totals.\n\n#### Signature\n\n```http\nGET /business-made/organization/chart () -> `{ people:[{id, name, departmentId, managerId, reports, reportsCount, chain, …}], departments, positions, unplaced, noDepartment, levels, ladderSet, totals:{people, departments, placed, unplaced, positions, openSeats} }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/organization/chart/people/{id}/department":{"post":{"operationId":"OrganizationController_placePerson","summary":"Put a person in a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_employee sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"`{ employeeId, departmentId, departmentName }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Department not found — `departmentId` names no department.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Department not found","path":"/business-made/organization/chart/people/{id}/department","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that sk.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/organization/chart/people/{id}/department","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Writes the department link and its name onto the person together, so every screen agrees. `departmentId: null` takes them out of any department.\n\n#### Signature\n\n```http\nPOST /business-made/organization/chart/people/{id}/department (id: string, body) -> `{ employeeId, departmentId, departmentName }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that sk. | — |\n| `400` | DEPARTMENT_NOT_FOUND | Department not found | `departmentId` names no department. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"departmentId":{"type":"string","nullable":true}}},"example":{"departmentId":"66f0c3a1e4b0a1b2c3d4e5f9"}}}}}},"/business-made/organization/chart/people/{id}/manager":{"post":{"operationId":"OrganizationController_setManager","summary":"Set who a person reports to","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_employee sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"`{ employeeId, supervisor, supervisorName }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Someone cannot report to themselves — `managerId` is the person.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Someone cannot report to themselves","path":"/business-made/organization/chart/people/{id}/manager","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Sets the person's manager — the same supervisor leave approvals route to. `managerId: null` clears it. Refuses a loop (the manager already reporting, directly or not, to this person).\n\n#### Signature\n\n```http\nPOST /business-made/organization/chart/people/{id}/manager (id: string, body) -> `{ employeeId, supervisor, supervisorName }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SELF_MANAGER | Someone cannot report to themselves | `managerId` is the person. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/leave/setup/supervisor`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"managerId":{"type":"string","nullable":true,"description":"Manager's bm_employee sk."}}},"example":{"managerId":"66f0c3a1e4b0a1b2c3d4e5f7"}}}}}},"/business-made/organization/chart/departments":{"post":{"operationId":"OrganizationController_saveDepartment","summary":"Create or edit a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ id }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Required: name — `name` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Required: name","path":"/business-made/organization/chart/departments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Department not found — Editing a department that does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Department not found","path":"/business-made/organization/chart/departments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Saves a department from the chart. With `sk` (or `id`) it edits that one. `name` is required; `code` is made from the name when left blank. A rename is carried onto the people linked to the department; `parentDepartmentId` nests it (with the loop check of the parent endpoint).\n\n#### Signature\n\n```http\nPOST /business-made/organization/chart/departments (body) -> `{ id }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DEPARTMENT_REQUIRED | Required: name | `name` is missing. | — |\n| `404` | DEPARTMENT_NOT_FOUND | Department not found | Editing a department that does not exist. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"sk":{"type":"string"},"name":{"type":"string"},"code":{"type":"string","description":"Upper-cased; made from the name when blank."},"description":{"type":"string"},"status":{"type":"string","default":"active"},"type":{"type":"string","default":"department"},"headId":{"type":"string"},"parentDepartmentId":{"type":"string","nullable":true}}},"example":{"name":"Kitchen"}}}}}},"/business-made/organization/chart/departments/{id}/parent":{"post":{"operationId":"OrganizationController_setDepartmentParent","summary":"Nest a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Department sk.","example":"66f0c3a1e4b0a1b2c3d4e5f9"}],"responses":{"201":{"description":"`{ id, parentId }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"That would put a department inside its own sub-department — The parent is the department itself or one of its sub-departments.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"That would put a department inside its own sub-department","path":"/business-made/organization/chart/departments/{id}/parent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Department not found — No department has that id (400 when it is the parent that is missing).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Department not found","path":"/business-made/organization/chart/departments/{id}/parent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Puts the department under another, or makes it top-level with `parentId: null`. Refuses loops.\n\n#### Signature\n\n```http\nPOST /business-made/organization/chart/departments/{id}/parent (id: string, body) -> `{ id, parentId }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEPARTMENT_NOT_FOUND | Department not found | No department has that id (400 when it is the parent that is missing). | — |\n| `400` | DEPARTMENT_LOOP | That would put a department inside its own sub-department | The parent is the department itself or one of its sub-departments. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parentId":{"type":"string","nullable":true}}},"example":{"parentId":"66f0c3a1e4b0a1b2c3d4e5fa"}}}}}},"/business-made/organization/chart/departments/adopt":{"post":{"operationId":"OrganizationController_adoptDepartment","summary":"Adopt a department name people already carry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ id, name, linked }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Required: name — `name` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Required: name","path":"/business-made/organization/chart/departments/adopt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Creates a department for a name already on people's records (e.g. \"Kitchen\") and links everyone carrying that name to it.\n\n#### Signature\n\n```http\nPOST /business-made/organization/chart/departments/adopt (body) -> `{ id, name, linked }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | DEPARTMENT_REQUIRED | Required: name | `name` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"example":{"name":"Kitchen"}}}}}},"/business-made/organization/chart/positions":{"post":{"operationId":"OrganizationController_savePosition","summary":"Create or edit a position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ id }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Required: title, departmentId — A required field is missing; the body lists `fields`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Required: title, departmentId","path":"/business-made/organization/chart/positions","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Saves a position — a role in a department with a number of approved seats (`headcount.approved`, at least 1). With `sk` (or `id`) it edits that one.\n\n#### Signature\n\n```http\nPOST /business-made/organization/chart/positions (body) -> `{ id }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | POSITION_REQUIRED | Required: title, departmentId | A required field is missing; the body lists `fields`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","departmentId"],"properties":{"sk":{"type":"string"},"title":{"type":"string"},"departmentId":{"type":"string"},"code":{"type":"string"},"level":{"type":"string"},"employmentType":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","default":"active"},"headcount":{"type":"object","properties":{"approved":{"type":"integer","minimum":1}}}}},"example":{"title":"Line cook","departmentId":"66f0c3a1e4b0a1b2c3d4e5f9","headcount":{"approved":4}}}}}}},"/business-made/organization/departments/hierarchy":{"get":{"operationId":"OrganizationController_getDepartmentHierarchy","summary":"Get the department hierarchy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The department hierarchy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"The full department tree in one call — what an org-structure view renders, rather than walking children level by level.\n\n#### Signature\n\n```http\nGET /business-made/organization/departments/hierarchy () -> The department hierarchy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/charts`"}},"/business-made/organization/departments":{"get":{"operationId":"OrganizationController_getDepartments","summary":"List departments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Departments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"The org's departments.\n\n#### Signature\n\n```http\nGET /business-made/organization/departments () -> Departments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/departments/hierarchy`"},"post":{"operationId":"OrganizationController_createDepartment","summary":"Create a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created department","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Creates a department. Give it a `parentId` to nest it under another, which is what builds the hierarchy.\n\n#### Signature\n\n```http\nPOST /business-made/organization/departments (body) -> The created department\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/departments/{id}/head`","requestBody":{"description":"The department to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"ENG","name":"Engineering","parentId":"DEP-tech"}}}}}},"/business-made/organization/departments/{id}":{"get":{"operationId":"OrganizationController_getDepartment","summary":"Get a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Department id.","example":"DEP-eng"}],"responses":{"200":{"description":"The department","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/organization/departments/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Fetches one department with its head and headcount.\n\n#### Signature\n\n```http\nGET /business-made/organization/departments/{id} (id: string) -> The department\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/departments/{id}/children`"},"delete":{"operationId":"OrganizationController_deleteDepartment","summary":"Delete a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Department id.","example":"DEP-eng"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Deletes a department. Employees and child departments still pointing at it are not moved — reassign them first, or they end up orphaned in the hierarchy.\n\n#### Signature\n\n```http\nDELETE /business-made/organization/departments/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Nothing checks for members or children first.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/departments/{id}/children`"}},"/business-made/organization/departments/update":{"post":{"operationId":"OrganizationController_updateDepartment","summary":"Update a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated department","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Updates a department. Changing `parentId` moves it — and everything beneath it — in the hierarchy. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/organization/departments/update (body) -> The updated department\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/departments/hierarchy`","requestBody":{"description":"The department to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"DEP-eng","data":{"name":"Engineering & Platform"}}}}}}},"/business-made/organization/departments/code/{code}":{"get":{"operationId":"OrganizationController_getDepartmentByCode","summary":"Get a department by code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"path","schema":{"type":"string"},"description":"Department code.","example":"ENG"}],"responses":{"200":{"description":"The department","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Resolves a department from its business code rather than its record id.\n\n#### Signature\n\n```http\nGET /business-made/organization/departments/code/{code} (code: string) -> The department\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/departments/{id}`"}},"/business-made/organization/departments/status/active":{"get":{"operationId":"OrganizationController_getActiveDepartments","summary":"List active departments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active departments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Departments currently in use, excluding any that have been retired.\n\n#### Signature\n\n```http\nGET /business-made/organization/departments/status/active () -> Active departments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/departments`"}},"/business-made/organization/departments/{id}/children":{"get":{"operationId":"OrganizationController_getSubDepartments","summary":"Get child departments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Department id.","example":"DEP-tech"}],"responses":{"200":{"description":"Child departments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"The departments directly beneath one in the hierarchy — one level, not the whole subtree.\n\n#### Signature\n\n```http\nGET /business-made/organization/departments/{id}/children (id: string) -> Child departments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/departments/hierarchy`"}},"/business-made/organization/departments/{id}/head":{"post":{"operationId":"OrganizationController_updateDepartmentHead","summary":"Set a department head","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Department id.","example":"DEP-eng"}],"responses":{"201":{"description":"The updated department","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Department not found — No department matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Department not found","path":"/business-made/organization/departments/{id}/head","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Assigns an employee as head of a department.\n\n#### Signature\n\n```http\nPOST /business-made/organization/departments/{id}/head (id: string, body) -> The updated department\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEPARTMENT_NOT_FOUND | Department not found | No department matches. | The error body carries `code: \"DEPARTMENT_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/departments/{id}/headcount`","requestBody":{"description":"Who leads it.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4001"}}}}}},"/business-made/organization/departments/{id}/headcount":{"post":{"operationId":"OrganizationController_updateDepartmentHeadcount","summary":"Set a department headcount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Department id.","example":"DEP-eng"}],"responses":{"201":{"description":"The updated department","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Department not found — No department matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Department not found","path":"/business-made/organization/departments/{id}/headcount","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Sets the approved headcount for a department — the establishment figure vacancies are counted against, not the number of people currently in post.\n\n#### Signature\n\n```http\nPOST /business-made/organization/departments/{id}/headcount (id: string, body) -> The updated department\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- This is the budgeted establishment, not actual headcount — compare against the headcount report.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DEPARTMENT_NOT_FOUND | Department not found | No department matches. | The error body carries `code: \"DEPARTMENT_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/reports/headcount`","requestBody":{"description":"The approved headcount.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"headcount":24}}}}}},"/business-made/organization/positions":{"get":{"operationId":"OrganizationController_getPositions","summary":"List positions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Positions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"The defined positions — the roles that exist, filled or not.\n\n#### Signature\n\n```http\nGET /business-made/organization/positions () -> Positions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/positions/status/open`"},"post":{"operationId":"OrganizationController_createPosition","summary":"Create a position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Creates a position within a department. A position exists independently of whoever holds it, which is what lets a vacancy be tracked.\n\n#### Signature\n\n```http\nPOST /business-made/organization/positions (body) -> The created position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/positions/{id}/fill`","requestBody":{"description":"The position to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"ENG-LEAD","title":"Engineering Lead","departmentId":"DEP-eng"}}}}}},"/business-made/organization/positions/{id}":{"get":{"operationId":"OrganizationController_getPosition","summary":"Get a position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Position id.","example":"POS-eng-lead"}],"responses":{"200":{"description":"The position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/organization/positions/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Fetches one position with its department and current holder.\n\n#### Signature\n\n```http\nGET /business-made/organization/positions/{id} (id: string) -> The position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/positions/{id}/fill`"},"delete":{"operationId":"OrganizationController_deletePosition","summary":"Delete a position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Position id.","example":"POS-eng-lead"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Deletes a position. Vacate it first if someone holds it — nothing here checks.\n\n#### Signature\n\n```http\nDELETE /business-made/organization/positions/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/positions/{id}/vacate`"}},"/business-made/organization/positions/update":{"post":{"operationId":"OrganizationController_updatePosition","summary":"Update a position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Updates a position's details. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/organization/positions/update (body) -> The updated position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/positions/{id}`","requestBody":{"description":"The position to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"POS-eng-lead","data":{"title":"Principal Engineer"}}}}}}},"/business-made/organization/positions/code/{code}":{"get":{"operationId":"OrganizationController_getPositionByCode","summary":"Get a position by code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"path","schema":{"type":"string"},"description":"Position code.","example":"ENG-LEAD"}],"responses":{"200":{"description":"The position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Resolves a position from its business code.\n\n#### Signature\n\n```http\nGET /business-made/organization/positions/code/{code} (code: string) -> The position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/positions/{id}`"}},"/business-made/organization/positions/status/active":{"get":{"operationId":"OrganizationController_getActivePositions","summary":"List active positions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active positions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Positions currently in use, whether or not they are filled.\n\n#### Signature\n\n```http\nGET /business-made/organization/positions/status/active () -> Active positions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/positions/status/open`"}},"/business-made/organization/positions/department/{departmentId}":{"get":{"operationId":"OrganizationController_getPositionsByDepartment","summary":"List positions in a department","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"departmentId","required":true,"in":"path","schema":{"type":"string"},"description":"Department id.","example":"DEP-eng"}],"responses":{"200":{"description":"Positions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"The positions belonging to one department.\n\n#### Signature\n\n```http\nGET /business-made/organization/positions/department/{departmentId} (departmentId: string) -> Positions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/positions`"}},"/business-made/organization/positions/status/open":{"get":{"operationId":"OrganizationController_getOpenPositions","summary":"List open positions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Open positions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Vacancies — positions that exist but have nobody in them. What recruitment works from.\n\n#### Signature\n\n```http\nGET /business-made/organization/positions/status/open () -> Open positions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/positions/{id}/fill`"}},"/business-made/organization/positions/{id}/fill":{"post":{"operationId":"OrganizationController_fillPosition","summary":"Fill a position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Position id.","example":"POS-eng-lead"}],"responses":{"201":{"description":"The updated position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Position not found — No position matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Position not found","path":"/business-made/organization/positions/{id}/fill","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Records an employee as taking a position, closing the vacancy.\n\n#### Signature\n\n```http\nPOST /business-made/organization/positions/{id}/fill (id: string, body) -> The updated position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POSITION_NOT_FOUND | Position not found | No position matches. | The error body carries `code: \"POSITION_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/positions/{id}/vacate`","requestBody":{"description":"Who fills it.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","effectiveDate":"2026-09-01"}}}}}},"/business-made/organization/positions/{id}/vacate":{"post":{"operationId":"OrganizationController_vacatePosition","summary":"Vacate a position","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Position id.","example":"POS-eng-lead"}],"responses":{"201":{"description":"The updated position","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Position not found — No position matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Position not found","path":"/business-made/organization/positions/{id}/vacate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Records a position as empty again — after a departure or a move — putting it back on the open list.\n\n#### Signature\n\n```http\nPOST /business-made/organization/positions/{id}/vacate (id: string, body) -> The updated position\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POSITION_NOT_FOUND | Position not found | No position matches. | The error body carries `code: \"POSITION_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/positions/status/open`","requestBody":{"description":"Optional details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"effectiveDate":"2026-09-30","reason":"Resignation"}}}}}},"/business-made/organization/charts":{"get":{"operationId":"OrganizationController_getOrgCharts","summary":"List org charts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Org charts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"The organization charts defined for the org. More than one can exist — a current structure and a proposed one, for instance.\n\n#### Signature\n\n```http\nGET /business-made/organization/charts () -> Org charts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/charts/default`"},"post":{"operationId":"OrganizationController_createOrgChart","summary":"Create an org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created chart","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Creates an org chart. It starts unpublished, so a restructure can be drafted without anyone seeing it.\n\n#### Signature\n\n```http\nPOST /business-made/organization/charts (body) -> The created chart\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/charts/{id}/publish`","requestBody":{"description":"The chart to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"2026 structure","description":"Post-reorg"}}}}}},"/business-made/organization/charts/{id}":{"get":{"operationId":"OrganizationController_getOrgChart","summary":"Get an org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Chart id.","example":"ORG-2026"}],"responses":{"200":{"description":"The org chart","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/organization/charts/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Fetches one org chart.\n\n#### Signature\n\n```http\nGET /business-made/organization/charts/{id} (id: string) -> The org chart\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/charts/{id}/publish`"},"delete":{"operationId":"OrganizationController_deleteOrgChart","summary":"Delete an org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Chart id.","example":"ORG-2026"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Deletes a chart. Archive it instead to keep a record of a past structure.\n\n#### Signature\n\n```http\nDELETE /business-made/organization/charts/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/charts/{id}/archive`"}},"/business-made/organization/charts/update":{"post":{"operationId":"OrganizationController_updateOrgChart","summary":"Update an org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated chart","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Updates a chart's structure or details. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/organization/charts/update (body) -> The updated chart\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/charts/{id}/publish`","requestBody":{"description":"The chart to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"ORG-2026","data":{"name":"2026 structure (final)"}}}}}}},"/business-made/organization/charts/{id}/publish":{"post":{"operationId":"OrganizationController_publishOrgChart","summary":"Publish an org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Chart id.","example":"ORG-2026"}],"responses":{"201":{"description":"The published chart","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Org chart not found — No org chart matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Org chart not found","path":"/business-made/organization/charts/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Makes a chart visible across the organization. This is when a restructure becomes public — check it before publishing.\n\n#### Signature\n\n```http\nPOST /business-made/organization/charts/{id}/publish (id: string) -> The published chart\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORG_CHART_NOT_FOUND | Org chart not found | No org chart matches. | The error body carries `code: \"ORG_CHART_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/charts/{id}/archive`"}},"/business-made/organization/charts/{id}/archive":{"post":{"operationId":"OrganizationController_archiveOrgChart","summary":"Archive an org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Chart id.","example":"ORG-2025"}],"responses":{"201":{"description":"The archived chart","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Org chart not found — No org chart matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Org chart not found","path":"/business-made/organization/charts/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Retires a chart while keeping it — how the history of past structures is preserved.\n\n#### Signature\n\n```http\nPOST /business-made/organization/charts/{id}/archive (id: string) -> The archived chart\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORG_CHART_NOT_FOUND | Org chart not found | No org chart matches. | The error body carries `code: \"ORG_CHART_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/organization/charts/{id}`"}},"/business-made/organization/charts/default":{"get":{"operationId":"OrganizationController_getDefaultOrgChart","summary":"Get the default org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The default org chart","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"The chart marked as the org's current structure — what to render when no particular chart was asked for.\n\n#### Signature\n\n```http\nGET /business-made/organization/charts/default () -> The default org chart\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/organization/charts/{id}/set-default`"}},"/business-made/organization/charts/{id}/set-default":{"post":{"operationId":"OrganizationController_setDefaultOrgChart","summary":"Set the default org chart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Chart id.","example":"ORG-2026"}],"responses":{"201":{"description":"The updated chart","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Org chart not found — No org chart matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Org chart not found","path":"/business-made/organization/charts/{id}/set-default","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Marks a chart as the current structure. Setting a new default clears the flag on the previous one.\n\n#### Signature\n\n```http\nPOST /business-made/organization/charts/{id}/set-default (id: string) -> The updated chart\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ORG_CHART_NOT_FOUND | Org chart not found | No org chart matches. | The error body carries `code: \"ORG_CHART_NOT_FOUND\"` and the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/organization/charts/default`"}},"/business-made/organization/metrics":{"get":{"operationId":"OrganizationController_getOrganizationMetrics","summary":"Get organization metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Organization metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Organization"],"description":"Structural figures — department and position counts, vacancy rates, spans of control.\n\n#### Signature\n\n```http\nGET /business-made/organization/metrics () -> Organization metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/employees/reports/headcount`"}},"/business-made/documents/employee-documents":{"get":{"operationId":"DocumentsController_getEmployeeDocuments","summary":"List employee documents","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Employee documents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"HR documents across the org — contracts, right-to-work evidence, certificates. Sensitive personal data; restrict access.\n\n#### Signature\n\n```http\nGET /business-made/documents/employee-documents () -> Employee documents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Often includes identity documents. Access should be narrow and logged.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/employee-documents/employee/{employeeId}`"},"post":{"operationId":"DocumentsController_createEmployeeDocument","summary":"Upload an employee document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Records a document against an employee. Set an expiry where the document has one — that is what puts it on the expiring list later.\n\n#### Signature\n\n```http\nPOST /business-made/documents/employee-documents (body) -> The created document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/employee-documents/{id}/verify`","requestBody":{"description":"The document to record.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","category":"right-to-work","title":"Passport","fileUrl":"https://cdn.appmint.io/hr/emp-4821-passport.pdf","expiresAt":"2030-04-12"}}}}}},"/business-made/documents/employee-documents/expiring":{"get":{"operationId":"DocumentsController_getExpiringDocuments","summary":"List expiring documents","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"daysAhead","required":true,"in":"query","schema":{"type":"number"}},{"name":"days","in":"query","required":false,"description":"Look-ahead window in days.","schema":{"type":"integer"},"example":60}],"responses":{"200":{"description":"Expiring documents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Documents approaching or past their expiry — visas, work permits, professional registrations.\n\nThis is a compliance worklist rather than a convenience: continuing to employ someone whose right-to-work evidence has lapsed is an offence in many jurisdictions.\n\n#### Signature\n\n```http\nGET /business-made/documents/employee-documents/expiring (days?: integer) -> Expiring documents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/employee-documents/{id}/verify`"}},"/business-made/documents/employee-documents/{id}/file":{"get":{"operationId":"DocumentsController_getDocumentFile","summary":"Open a document's file","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"`{ url, name }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/business-made/documents/employee-documents/{id}/file","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"A short-lived signed link to the document's stored file. A document whose file is an external URL returns that URL; one with no file returns `url: null`.\n\n#### Signature\n\n```http\nGET /business-made/documents/employee-documents/{id}/file (id: string) -> `{ url, name }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DOCUMENT_NOT_FOUND | Document not found | No document has that id. | The body carries `code: \"DOCUMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/documents/employee-documents/{id}/reject":{"post":{"operationId":"DocumentsController_rejectDocument","summary":"Reject a document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The rejected document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Say why the document is rejected — `reason` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say why the document is rejected","path":"/business-made/documents/employee-documents/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/business-made/documents/employee-documents/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Rejects an uploaded document with the reason the person sees, so they can upload it again.\n\n#### Signature\n\n```http\nPOST /business-made/documents/employee-documents/{id}/reject (id: string, body) -> The rejected document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DOCUMENT_NOT_FOUND | Document not found | No document has that id. | The body carries `code: \"DOCUMENT_NOT_FOUND\"` and the id. |\n| `400` | reason-required | Say why the document is rejected | `reason` is empty. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}},"example":{"reason":"The scan is cut off — both sides are needed"}}}}}},"/business-made/documents/employee-documents/{id}/restore":{"post":{"operationId":"DocumentsController_restoreDocument","summary":"Restore an archived document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The restored document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/business-made/documents/employee-documents/{id}/restore","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Brings an archived document back.\n\n#### Signature\n\n```http\nPOST /business-made/documents/employee-documents/{id}/restore (id: string) -> The restored document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DOCUMENT_NOT_FOUND | Document not found | No document has that id. | The body carries `code: \"DOCUMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/documents/employee-documents/{id}":{"get":{"operationId":"DocumentsController_getEmployeeDocument","summary":"Get an employee document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/documents/employee-documents/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Fetches one document with its verification and signature state.\n\n#### Signature\n\n```http\nGET /business-made/documents/employee-documents/{id} (id: string) -> The document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/employee-documents/{id}/verify`"},"delete":{"operationId":"DocumentsController_deleteEmployeeDocument","summary":"Delete an employee document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Deletes a document. Some HR documents carry statutory retention periods — archive instead unless you are certain deletion is permitted.\n\n#### Signature\n\n```http\nDELETE /business-made/documents/employee-documents/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Check retention obligations before deleting.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/employee-documents/{id}/archive`"}},"/business-made/documents/employee-documents/update":{"post":{"operationId":"DocumentsController_updateEmployeeDocument","summary":"Update an employee document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Updates a document record. Re-verify after replacing the underlying file — verification refers to what was checked, not to the record. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/documents/employee-documents/update (body) -> The updated document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/employee-documents/{id}/verify`","requestBody":{"description":"The document to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"DOC-4821","data":{"expiresAt":"2032-04-12"}}}}}}},"/business-made/documents/employee-documents/employee/{employeeId}":{"get":{"operationId":"DocumentsController_getDocumentsByEmployee","summary":"Get an employee's documents","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The employee's documents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Every document held for one employee.\n\n#### Signature\n\n```http\nGET /business-made/documents/employee-documents/employee/{employeeId} (employeeId: string) -> The employee's documents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/employee-documents/employee/{employeeId}/category/{category}`"}},"/business-made/documents/employee-documents/employee/{employeeId}/category/{category}":{"get":{"operationId":"DocumentsController_getDocumentsByCategory","summary":"Get an employee's documents by category","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"category","required":true,"in":"path","schema":{"type":"string"},"description":"Document category.","example":"right-to-work"}],"responses":{"200":{"description":"Matching documents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"One employee's documents within a category — right-to-work evidence, say, rather than everything on file.\n\n#### Signature\n\n```http\nGET /business-made/documents/employee-documents/employee/{employeeId}/category/{category} (employeeId: string, category: string) -> Matching documents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/employee-documents/expiring`"}},"/business-made/documents/employee-documents/{id}/verify":{"post":{"operationId":"DocumentsController_verifyDocument","summary":"Verify a document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The verified document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/business-made/documents/employee-documents/{id}/verify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Records that someone checked the document against the original. For right-to-work evidence the verification — who checked, and when — is the defence, not the copy.\n\n#### Signature\n\n```http\nPOST /business-made/documents/employee-documents/{id}/verify (id: string, body) -> The verified document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DOCUMENT_NOT_FOUND | Document not found | No document has that id. | The body carries `code: \"DOCUMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/employee-documents/expiring`","requestBody":{"description":"Verification details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"verifiedBy":"hr@acme.com","verifiedDate":"2026-09-01","method":"original-seen"}}}}}},"/business-made/documents/employee-documents/{id}/sign":{"post":{"operationId":"DocumentsController_signDocument","summary":"Sign a document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The signed document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/business-made/documents/employee-documents/{id}/sign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Records a signature on an employee document, such as a contract.\n\n#### Signature\n\n```http\nPOST /business-made/documents/employee-documents/{id}/sign (id: string, body) -> The signed document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DOCUMENT_NOT_FOUND | Document not found | No document has that id. | The body carries `code: \"DOCUMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/signed-documents`","requestBody":{"description":"Signature details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"signedBy":"EMP-4821","signedDate":"2026-09-01"}}}}}},"/business-made/documents/employee-documents/{id}/archive":{"post":{"operationId":"DocumentsController_archiveDocument","summary":"Archive a document","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The archived document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Document not found — No document has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Document not found","path":"/business-made/documents/employee-documents/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Retires a document while retaining it — the correct way to supersede one without breaching retention rules.\n\n#### Signature\n\n```http\nPOST /business-made/documents/employee-documents/{id}/archive (id: string) -> The archived document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DOCUMENT_NOT_FOUND | Document not found | No document has that id. | The body carries `code: \"DOCUMENT_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/documents/employee-documents/{id}`"}},"/business-made/documents/policies":{"get":{"operationId":"DocumentsController_getPolicies","summary":"List policies","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Policies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Company policies, published or not.\n\n#### Signature\n\n```http\nGET /business-made/documents/policies () -> Policies\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/policies/status/active`"},"post":{"operationId":"DocumentsController_createPolicy","summary":"Create a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Creates a policy as a draft. Publishing is what makes it effective, and assignment is what obliges people to acknowledge it.\n\n#### Signature\n\n```http\nPOST /business-made/documents/policies (body) -> The created policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/publish`","requestBody":{"description":"The policy to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"SEC-01","title":"Acceptable use policy","category":"security","content":"…","version":"1.0"}}}}}},"/business-made/documents/policies/{id}":{"get":{"operationId":"DocumentsController_getPolicy","summary":"Get a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/documents/policies/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Fetches one policy with its version and content.\n\n#### Signature\n\n```http\nGET /business-made/documents/policies/{id} (id: string) -> The policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/version`"},"delete":{"operationId":"DocumentsController_deletePolicy","summary":"Delete a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Deletes a policy. Acknowledgement records referencing it lose the text they refer to — archive instead.\n\n#### Signature\n\n```http\nDELETE /business-made/documents/policies/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/archive`"}},"/business-made/documents/policies/update":{"post":{"operationId":"DocumentsController_updatePolicy","summary":"Update a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Updates a policy in place.\n\nFor any substantive change, **create a new version instead**. Editing a published policy silently changes what people already acknowledged, and their acknowledgement then refers to text that no longer exists.\n\n#### Signature\n\n```http\nPOST /business-made/documents/policies/update (body) -> The updated policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing acknowledgements are not invalidated by an edit — that is why versioning exists.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/version`","requestBody":{"description":"The policy to update. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"POL-sec01","data":{"title":"Acceptable use policy (IT)"}}}}}}},"/business-made/documents/policies/{id}/publish":{"post":{"operationId":"DocumentsController_publishPolicy","summary":"Publish a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The published policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy not found — No policy has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy not found","path":"/business-made/documents/policies/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Brings a policy into force. Assign it afterwards to require acknowledgement — publishing alone does not tell anyone.\n\n#### Signature\n\n```http\nPOST /business-made/documents/policies/{id}/publish (id: string) -> The published policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POLICY_NOT_FOUND | Policy not found | No policy has that id. | The body carries `code: \"POLICY_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/assign`"}},"/business-made/documents/policies/{id}/archive":{"post":{"operationId":"DocumentsController_archivePolicy","summary":"Archive a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The archived policy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy not found — No policy has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy not found","path":"/business-made/documents/policies/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Retires a policy while keeping it and its acknowledgement history — necessary to show what was in force at a past date.\n\n#### Signature\n\n```http\nPOST /business-made/documents/policies/{id}/archive (id: string) -> The archived policy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POLICY_NOT_FOUND | Policy not found | No policy has that id. | The body carries `code: \"POLICY_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/documents/policies/{id}`"}},"/business-made/documents/policies/{id}/version":{"post":{"operationId":"DocumentsController_createPolicyVersion","summary":"Create a new policy version","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The new version","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Say what changed in this version — `changes` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say what changed in this version","path":"/business-made/documents/policies/{id}/version","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy not found — No policy has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy not found","path":"/business-made/documents/policies/{id}/version","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Creates a new version of a policy, preserving the old one and the acknowledgements against it.\n\nThis is the correct way to change a policy people have already agreed to: their acknowledgement stays attached to the text they actually saw, and the new version can be re-assigned for fresh acknowledgement.\n\n#### Signature\n\n```http\nPOST /business-made/documents/policies/{id}/version (id: string, body) -> The new version\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POLICY_NOT_FOUND | Policy not found | No policy has that id. | The body carries `code: \"POLICY_NOT_FOUND\"` and the id. |\n| `400` | CHANGES_REQUIRED | Say what changed in this version | `changes` is empty. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/assign`","requestBody":{"description":"The new version.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"version":"2.0","content":"…","changeSummary":"Added remote-working provisions"}}}}}},"/business-made/documents/policies/status/active":{"get":{"operationId":"DocumentsController_getActivePolicies","summary":"List active policies","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active policies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Policies currently in force — what employees are actually bound by.\n\n#### Signature\n\n```http\nGET /business-made/documents/policies/status/active () -> Active policies\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/assign`"}},"/business-made/documents/policies/category/{category}":{"get":{"operationId":"DocumentsController_getPoliciesByCategory","summary":"List policies by category","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"category","required":true,"in":"path","schema":{"type":"string"},"description":"Policy category.","example":"security"}],"responses":{"200":{"description":"Policies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Policies within one category.\n\n#### Signature\n\n```http\nGET /business-made/documents/policies/category/{category} (category: string) -> Policies\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/policies`"}},"/business-made/documents/acknowledgements":{"get":{"operationId":"DocumentsController_getPolicyAcknowledgements","summary":"List policy acknowledgements","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Acknowledgements","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Acknowledgement records across the org — who has confirmed which policy, and who has not. The compliance position.\n\n#### Signature\n\n```http\nGET /business-made/documents/acknowledgements () -> Acknowledgements\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/acknowledgements/employee/{employeeId}`"},"post":{"operationId":"DocumentsController_createPolicyAcknowledgement","summary":"Create an acknowledgement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created acknowledgement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Creates a single acknowledgement requirement. Use the policy assign endpoint for a group.\n\n#### Signature\n\n```http\nPOST /business-made/documents/acknowledgements (body) -> The created acknowledgement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/assign`","requestBody":{"description":"The acknowledgement to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","policyId":"POL-sec01","dueDate":"2026-10-31"}}}}}},"/business-made/documents/acknowledgements/{id}":{"get":{"operationId":"DocumentsController_getPolicyAcknowledgement","summary":"Get an acknowledgement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The acknowledgement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/documents/acknowledgements/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Fetches one acknowledgement record with its status and date.\n\n#### Signature\n\n```http\nGET /business-made/documents/acknowledgements/{id} (id: string) -> The acknowledgement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/acknowledgements/{id}/acknowledge`"}},"/business-made/documents/acknowledgements/{id}/acknowledge":{"post":{"operationId":"DocumentsController_acknowledgePolicy","summary":"Acknowledge a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The acknowledgement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy acknowledgement not found — No acknowledgement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy acknowledgement not found","path":"/business-made/documents/acknowledgements/{id}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Records that an employee has read and accepted a policy — the evidence that they were bound by it.\n\nThe record ties the employee to a specific policy **version**, which is why substantive changes should be versioned rather than edited in place.\n\n#### Signature\n\n```http\nPOST /business-made/documents/acknowledgements/{id}/acknowledge (id: string, body) -> The acknowledgement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ACK_NOT_FOUND | Policy acknowledgement not found | No acknowledgement has that id. | The body carries `code: \"ACK_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/policies/{id}/version`","requestBody":{"description":"Optional detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"acknowledgedDate":"2026-10-05"}}}}}},"/business-made/documents/acknowledgements/{id}/decline":{"post":{"operationId":"DocumentsController_declinePolicy","summary":"Decline a policy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The declined acknowledgement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy acknowledgement not found — No acknowledgement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy acknowledgement not found","path":"/business-made/documents/acknowledgements/{id}/decline","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Records that an employee declined to acknowledge, with their reason. Worth capturing rather than leaving outstanding — a refusal is a different situation from an omission, and needs a different response.\n\n#### Signature\n\n```http\nPOST /business-made/documents/acknowledgements/{id}/decline (id: string, body) -> The declined acknowledgement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ACK_NOT_FOUND | Policy acknowledgement not found | No acknowledgement has that id. | The body carries `code: \"ACK_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/acknowledgements/{id}/acknowledge`","requestBody":{"description":"Why they declined.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Disputes the monitoring clause"}}}}}},"/business-made/documents/acknowledgements/{id}/waive":{"post":{"operationId":"DocumentsController_waiveAcknowledgement","summary":"Waive an acknowledgement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The waived acknowledgement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy acknowledgement not found — No acknowledgement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy acknowledgement not found","path":"/business-made/documents/acknowledgements/{id}/waive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Excuses an employee from acknowledging — a policy that does not apply to their role or location. Record why, so the gap in the compliance report is explained rather than merely absent.\n\n#### Signature\n\n```http\nPOST /business-made/documents/acknowledgements/{id}/waive (id: string, body) -> The waived acknowledgement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ACK_NOT_FOUND | Policy acknowledgement not found | No acknowledgement has that id. | The body carries `code: \"ACK_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/metrics`","requestBody":{"description":"Why it was waived.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Contractor — covered by their own agency policy"}}}}}},"/business-made/documents/acknowledgements/{id}/remind":{"post":{"operationId":"DocumentsController_sendReminder","summary":"Send an acknowledgement reminder","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"Dispatch result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy acknowledgement not found — No acknowledgement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy acknowledgement not found","path":"/business-made/documents/acknowledgements/{id}/remind","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Reminds an employee about an outstanding acknowledgement. Sends every time it is called — there is no cooldown.\n\n#### Signature\n\n```http\nPOST /business-made/documents/acknowledgements/{id}/remind (id: string) -> Dispatch result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ACK_NOT_FOUND | Policy acknowledgement not found | No acknowledgement has that id. | The body carries `code: \"ACK_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/acknowledgements/employee/{employeeId}`"}},"/business-made/documents/acknowledgements/employee/{employeeId}":{"get":{"operationId":"DocumentsController_getEmployeeAcknowledgements","summary":"Get an employee's acknowledgements","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The employee's acknowledgements","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"What one employee has been asked to acknowledge and what remains outstanding.\n\n#### Signature\n\n```http\nGET /business-made/documents/acknowledgements/employee/{employeeId} (employeeId: string) -> The employee's acknowledgements\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/documents/acknowledgements/{id}/remind`"}},"/business-made/documents/policies/{id}/assign":{"post":{"operationId":"DocumentsController_assignPolicyToEmployees","summary":"Assign a policy for acknowledgement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The created acknowledgements","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy not found — No policy has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy not found","path":"/business-made/documents/policies/{id}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Requires employees to acknowledge a policy, creating an acknowledgement record for each. The step that turns a published policy into an obligation.\n\n#### Signature\n\n```http\nPOST /business-made/documents/policies/{id}/assign (id: string, body) -> The created acknowledgements\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POLICY_NOT_FOUND | Policy not found | No policy has that id. | The body carries `code: \"POLICY_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/acknowledgements`","requestBody":{"description":"Who must acknowledge it.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeIds":["EMP-4821","EMP-4822"],"dueDate":"2026-10-31"}}}}}},"/business-made/documents/metrics":{"get":{"operationId":"DocumentsController_getDocumentsMetrics","summary":"Get documents metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Documents metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Documents"],"description":"Compliance figures — acknowledgement coverage per policy, outstanding requirements and expiring documents.\n\n#### Signature\n\n```http\nGET /business-made/documents/metrics () -> Documents metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/documents/employee-documents/expiring`"}},"/business-made/offboarding/cases":{"get":{"operationId":"OffboardingController_cases","summary":"Offboarding records with progress","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"`open`, or a status such as `completed`.","example":"open"}],"responses":{"200":{"description":"`{ counts:{open, completed, cancelled}, data:[row] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Every offboarding as a finished row — person, last day, status, task progress (`tasks: { total, done, openRequired }`), the steps allowed now (`moves`) and whether it can be completed — newest last day first, with counts of open, completed and cancelled. `status=open` shows everything not closed; any other value filters by status.\n\n#### Signature\n\n```http\nGET /business-made/offboarding/cases (status?: string) -> `{ counts:{open, completed, cancelled}, data:[row] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/offboarding/cases/{id}`"}},"/business-made/offboarding/cases/{id}":{"get":{"operationId":"OffboardingController_caseDetail","summary":"One offboarding with its checklist","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The offboarding detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/cases/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"The row plus its checklist and the steps allowed (`moves`: `start`/`cancel` when initiated, `complete`/`cancel` while in progress, none once closed).\n\n#### Signature\n\n```http\nGET /business-made/offboarding/cases/{id} (id: string) -> The offboarding detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/offboarding/cases/{id}/action":{"post":{"operationId":"OffboardingController_caseAction","summary":"Take an offboarding step","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The offboarding detail after the step","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"2 required tasks are still open — The step is not allowed now — already started, not in progress, required tasks open, already closed, no `taskId`, or an unknown action. The message says which.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"2 required tasks are still open","path":"/business-made/offboarding/cases/{id}/action","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/cases/{id}/action","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"One endpoint for every step, with the checks the screen should not have to make: `start`, `complete` (refused while required tasks are open), `cancel` (with `reason`), `task-done` and `task-na` (with `taskId`, optional `reason`). Returns the detail after the step.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/cases/{id}/action (id: string, body) -> The offboarding detail after the step\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n| `400` | OFFBOARDING_ACTION | 2 required tasks are still open | The step is not allowed now — already started, not in progress, required tasks open, already closed, no `taskId`, or an unknown action. The message says which. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["start","complete","cancel","task-done","task-na"]},"taskId":{"type":"string"},"reason":{"type":"string"}}},"example":{"action":"task-done","taskId":"return-laptop"}}}}}},"/business-made/offboarding":{"get":{"operationId":"OffboardingController_getOffboardings","summary":"List offboarding records","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Offboarding records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Offboarding across the org.\n\n#### Signature\n\n```http\nGET /business-made/offboarding () -> Offboarding records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/offboarding/status/active`"},"post":{"operationId":"OffboardingController_createOffboarding","summary":"Create an offboarding record","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Opens offboarding for a departing employee, generating the checklist. Terminating the employee record and offboarding them are separate steps — this is the operational one.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding (body) -> The created record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/employees/{id}/terminate`","requestBody":{"description":"The offboarding to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","lastWorkingDay":"2026-09-30","reason":"Resignation"}}}}}},"/business-made/offboarding/{id}":{"get":{"operationId":"OffboardingController_getOffboarding","summary":"Get an offboarding record","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The offboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/offboarding/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Fetches one offboarding record with its checklist and progress.\n\n#### Signature\n\n```http\nGET /business-made/offboarding/{id} (id: string) -> The offboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/start`"},"delete":{"operationId":"OffboardingController_deleteOffboarding","summary":"Delete an offboarding record","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Deletes an offboarding record and its checklist history.\n\n#### Signature\n\n```http\nDELETE /business-made/offboarding/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/cancel`"}},"/business-made/offboarding/update":{"post":{"operationId":"OffboardingController_updateOffboarding","summary":"Update an offboarding record","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Updates an offboarding record. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/update (body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/offboarding/{id}`","requestBody":{"description":"The record to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sk":"OFF-4821","data":{"lastWorkingDay":"2026-10-07"}}}}}}},"/business-made/offboarding/{id}/start":{"post":{"operationId":"OffboardingController_startOffboarding","summary":"Start offboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The started record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Begins the offboarding process, activating the checklist.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/start (id: string) -> The started record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/tasks/{taskId}/complete`"}},"/business-made/offboarding/{id}/tasks/{taskId}/complete":{"post":{"operationId":"OffboardingController_completeChecklistTask","summary":"Complete an offboarding task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id.","example":"TSK-laptop"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/tasks/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Marks one checklist task done.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/tasks/{taskId}/complete (id: string, taskId: string, body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/tasks/{taskId}/not-applicable`","requestBody":{"description":"Optional completion note.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Laptop returned and wiped"}}}}}},"/business-made/offboarding/{id}/tasks/{taskId}/not-applicable":{"post":{"operationId":"OffboardingController_markTaskNotApplicable","summary":"Mark an offboarding task not applicable","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id.","example":"TSK-car"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/tasks/{taskId}/not-applicable","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Marks a task as not relevant to this departure — no company car to return, no parking pass to surrender. Distinct from completing it, so the checklist stays truthful.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/tasks/{taskId}/not-applicable (id: string, taskId: string, body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/tasks/{taskId}/complete`","requestBody":{"description":"Why it does not apply.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Remote employee, no equipment issued"}}}}}},"/business-made/offboarding/{id}/equipment":{"post":{"operationId":"OffboardingController_recordEquipmentReturn","summary":"Record equipment return","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/equipment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Records company equipment returned by the departing employee. Anything outstanding at completion is worth resolving first — recovering a laptop after someone has left is much harder.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/equipment (id: string, body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/complete`","requestBody":{"description":"The equipment returned.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"items":[{"type":"laptop","assetTag":"LT-9912","condition":"good"}]}}}}}},"/business-made/offboarding/{id}/revoke-access":{"post":{"operationId":"OffboardingController_revokeAccess","summary":"Revoke system access","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/revoke-access","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Revokes the departing employee's system access.\n\nTiming matters more than most steps here: access left active after someone leaves is the security failure offboarding exists to prevent, and it is routinely the thing that gets forgotten.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/revoke-access (id: string, body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Record what was revoked — a partial revocation that looks complete is worse than none.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/complete`","requestBody":{"description":"Optional details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"effectiveAt":"2026-09-30T17:00:00.000Z","systems":["email","vpn","git"]}}}}}},"/business-made/offboarding/{id}/final-pay":{"post":{"operationId":"OffboardingController_processFinalPay","summary":"Record final pay","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/final-pay","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Records the final pay settlement — outstanding salary, accrued leave and any deductions. Many jurisdictions set a deadline for paying a leaver, so this is time-sensitive.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/final-pay (id: string, body) -> The updated record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/leave/balances/employee/{employeeId}`","requestBody":{"description":"Final pay details.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"finalPayDate":"2026-10-05","accruedLeaveDays":6.5,"deductions":[]}}}}}},"/business-made/offboarding/{id}/complete":{"post":{"operationId":"OffboardingController_completeOffboarding","summary":"Complete offboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The completed record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Closes the offboarding process. Check equipment, access revocation and final pay are all done first — completing with those outstanding leaves genuine loose ends.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/complete (id: string) -> The completed record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/revoke-access`"}},"/business-made/offboarding/{id}/cancel":{"post":{"operationId":"OffboardingController_cancelOffboarding","summary":"Cancel offboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Abandons offboarding — a resignation withdrawn, or a departure deferred. The record is kept.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{id}/cancel (id: string, body) -> The cancelled record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Restore any access already revoked — cancelling does not undo it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/revoke-access`","requestBody":{"description":"Why it was cancelled.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Employee retracted resignation"}}}}}},"/business-made/offboarding/status/active":{"get":{"operationId":"OffboardingController_getActiveOffboardings","summary":"List active offboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Active offboarding","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Departures currently in progress — who is leaving and what is outstanding.\n\n#### Signature\n\n```http\nGET /business-made/offboarding/status/active () -> Active offboarding\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/{id}/complete`"}},"/business-made/offboarding/exit-interviews":{"get":{"operationId":"OffboardingController_getExitInterviews","summary":"List exit interviews","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Exit interviews","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Exit interviews across the org — the data behind understanding why people leave.\n\n#### Signature\n\n```http\nGET /business-made/offboarding/exit-interviews () -> Exit interviews\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/offboarding/metrics`"}},"/business-made/offboarding/exit-interviews/{id}":{"get":{"operationId":"OffboardingController_getExitInterview","summary":"Get an exit interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The exit interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid id <id>","path":"/business-made/offboarding/exit-interviews/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Fetches one exit interview and its responses. Departing employees say candid things — restrict access to those who need it.\n\n#### Signature\n\n```http\nGET /business-made/offboarding/exit-interviews/{id} (id: string) -> The exit interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Candid feedback about named managers. Handle accordingly.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/exit-interviews/{id}/conduct`"}},"/business-made/offboarding/{offboardingId}/exit-interview":{"post":{"operationId":"OffboardingController_scheduleExitInterview","summary":"Schedule an exit interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"offboardingId","required":true,"in":"path","schema":{"type":"string"},"description":"Offboarding record id.","example":"OFF-4821"}],"responses":{"201":{"description":"The created exit interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Offboarding not found — No offboarding record has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Offboarding not found","path":"/business-made/offboarding/{offboardingId}/exit-interview","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Creates an exit interview against an offboarding record.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/{offboardingId}/exit-interview (offboardingId: string, body) -> The created exit interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | OFFBOARDING_NOT_FOUND | Offboarding not found | No offboarding record has that id. | The body carries `code: \"OFFBOARDING_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/exit-interviews/{id}/conduct`","requestBody":{"description":"Interview details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"scheduledDate":"2026-09-29","interviewer":"hr@acme.com"}}}}}},"/business-made/offboarding/exit-interviews/{id}/conduct":{"post":{"operationId":"OffboardingController_conductExitInterview","summary":"Record an exit interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The completed interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Exit interview not found — No exit interview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Exit interview not found","path":"/business-made/offboarding/exit-interviews/{id}/conduct","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Records the responses from a conducted exit interview.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/exit-interviews/{id}/conduct (id: string, body) -> The completed interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EXIT_INTERVIEW_NOT_FOUND | Exit interview not found | No exit interview has that id. | The body carries `code: \"EXIT_INTERVIEW_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/exit-interviews/{id}/action-items`","requestBody":{"description":"The responses.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"responses":[{"question":"Primary reason for leaving","answer":"Limited progression"}],"overallSentiment":"neutral"}}}}}},"/business-made/offboarding/exit-interviews/{id}/decline":{"post":{"operationId":"OffboardingController_declineExitInterview","summary":"Record a declined exit interview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Exit interview not found — No exit interview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Exit interview not found","path":"/business-made/offboarding/exit-interviews/{id}/decline","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Records that the employee declined to be interviewed. Worth capturing — a declined interview is a data point, and it distinguishes \"chose not to\" from \"was never asked\".\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/exit-interviews/{id}/decline (id: string, body) -> The updated interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EXIT_INTERVIEW_NOT_FOUND | Exit interview not found | No exit interview has that id. | The body carries `code: \"EXIT_INTERVIEW_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/offboarding/exit-interviews/{id}/conduct`","requestBody":{"description":"Optional detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Employee declined"}}}}}},"/business-made/offboarding/exit-interviews/{id}/action-items":{"post":{"operationId":"OffboardingController_addActionItem","summary":"Add exit interview action items","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated interview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Exit interview not found — No exit interview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Exit interview not found","path":"/business-made/offboarding/exit-interviews/{id}/action-items","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Records actions arising from an exit interview — the step that turns feedback into something that changes. Without it the interview is only a record.\n\n#### Signature\n\n```http\nPOST /business-made/offboarding/exit-interviews/{id}/action-items (id: string, body) -> The updated interview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EXIT_INTERVIEW_NOT_FOUND | Exit interview not found | No exit interview has that id. | The body carries `code: \"EXIT_INTERVIEW_NOT_FOUND\"` and the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/offboarding/metrics`","requestBody":{"description":"The actions.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"items":[{"action":"Review progression framework for engineering","owner":"hr@acme.com"}]}}}}}},"/business-made/offboarding/metrics":{"get":{"operationId":"OffboardingController_getOffboardingMetrics","summary":"Get offboarding metrics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"endDate","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Offboarding metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Offboarding"],"description":"Attrition figures and exit-interview themes — why people are leaving, aggregated.\n\n#### Signature\n\n```http\nGET /business-made/offboarding/metrics () -> Offboarding metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/offboarding/exit-interviews`"}},"/business-made/onboarding":{"get":{"operationId":"OnboardingController_list","summary":"List onboarding records","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Onboarding records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Onboarding in progress across the org — who is still being brought on, and how far along they are.\n\n#### Signature\n\n```http\nGET /business-made/onboarding () -> Onboarding records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}`"}},"/business-made/onboarding/{employeeId}":{"post":{"operationId":"OnboardingController_start","summary":"Start onboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/onboarding/{employeeId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Onboarding already in progress — The person already has an open onboarding.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Onboarding already in progress","path":"/business-made/onboarding/{employeeId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Starts the onboarding process for a new hire, generating the checklist from the configured template.\n\nThis is the HR process run **during hiring** — collecting documents, issuing equipment, granting access. It is not the staff portal, which serves people who are already onboarded.\n\n#### Signature\n\n```http\nPOST /business-made/onboarding/{employeeId} (employeeId: string, body) -> The onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id or code. | — |\n| `409` | ONBOARDING_IN_PROGRESS | Onboarding already in progress | The person already has an open onboarding. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}/items/{itemKey}/complete`","requestBody":{"description":"Optional overrides for the generated checklist.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"startDate":"2026-09-01"}}}}},"get":{"operationId":"OnboardingController_getOne","summary":"Get an employee's onboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"200":{"description":"The onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/onboarding/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"The onboarding checklist for one employee and its progress.\n\n#### Signature\n\n```http\nGET /business-made/onboarding/{employeeId} (employeeId: string) -> The onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id or code. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}/complete`"}},"/business-made/onboarding/{employeeId}/items/{itemKey}/complete":{"post":{"operationId":"OnboardingController_completeItem","summary":"Complete an onboarding item","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"itemKey","required":true,"in":"path","schema":{"type":"string"},"description":"Checklist item key.","example":"contract"}],"responses":{"201":{"description":"The updated onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Onboarding is already complete — The onboarding is complete (or cancelled: \"Onboarding is cancelled\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Onboarding is already complete","path":"/business-made/onboarding/{employeeId}/items/{itemKey}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/onboarding/{employeeId}/items/{itemKey}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Marks one checklist item done.\n\n#### Signature\n\n```http\nPOST /business-made/onboarding/{employeeId}/items/{itemKey}/complete (employeeId: string, itemKey: string, body) -> The updated onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id or code. | — |\n| `400` | ONBOARDING_CLOSED | Onboarding is already complete | The onboarding is complete (or cancelled: \"Onboarding is cancelled\"). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}/items/{itemKey}/skip`","requestBody":{"description":"Optional completion note.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Signed copy filed"}}}}}},"/business-made/onboarding/{employeeId}/items/{itemKey}/skip":{"post":{"operationId":"OnboardingController_skipItem","summary":"Skip an onboarding item","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"itemKey","required":true,"in":"path","schema":{"type":"string"},"description":"Checklist item key.","example":"parking-pass"}],"responses":{"201":{"description":"The updated onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Onboarding is already complete — The onboarding is complete (or cancelled: \"Onboarding is cancelled\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Onboarding is already complete","path":"/business-made/onboarding/{employeeId}/items/{itemKey}/skip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Item 'i9' is required and cannot be skipped — The item is required on the template.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Item 'i9' is required and cannot be skipped","path":"/business-made/onboarding/{employeeId}/items/{itemKey}/skip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Employee not found — No employee has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/onboarding/{employeeId}/items/{itemKey}/skip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Marks an item as not required for this hire — a step that does not apply to their role or location. Distinct from completing it, so the checklist stays honest.\n\n#### Signature\n\n```http\nPOST /business-made/onboarding/{employeeId}/items/{itemKey}/skip (employeeId: string, itemKey: string, body) -> The updated onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Record a reason — a skipped compliance step with no explanation is an audit problem.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id or code. | — |\n| `400` | ONBOARDING_CLOSED | Onboarding is already complete | The onboarding is complete (or cancelled: \"Onboarding is cancelled\"). | — |\n| `403` | ITEM_REQUIRED | Item 'i9' is required and cannot be skipped | The item is required on the template. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}/items/{itemKey}/complete`","requestBody":{"description":"Why it was skipped.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Remote employee — no parking needed"}}}}}},"/business-made/onboarding/{employeeId}/complete":{"post":{"operationId":"OnboardingController_complete","summary":"Complete onboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The completed onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Onboarding is already complete — The onboarding is complete (or cancelled: \"Onboarding is cancelled\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Onboarding is already complete","path":"/business-made/onboarding/{employeeId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/onboarding/{employeeId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"412":{"description":"Required items still pending — Required items are still open; the body lists them in `outstanding`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Required items still pending","path":"/business-made/onboarding/{employeeId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Closes the onboarding process. Outstanding items should be completed or skipped first — closing with items open leaves the checklist unresolved.\n\n#### Signature\n\n```http\nPOST /business-made/onboarding/{employeeId}/complete (employeeId: string) -> The completed onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id or code. | — |\n| `400` | ONBOARDING_CLOSED | Onboarding is already complete | The onboarding is complete (or cancelled: \"Onboarding is cancelled\"). | — |\n| `412` | ITEMS_PENDING | Required items still pending | Required items are still open; the body lists them in `outstanding`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/onboarding/{employeeId}`"}},"/business-made/onboarding/{employeeId}/cancel":{"post":{"operationId":"OnboardingController_cancel","summary":"Cancel onboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"}],"responses":{"201":{"description":"The cancelled onboarding record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Onboarding is already complete — The onboarding is complete (or cancelled: \"Onboarding is cancelled\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Onboarding is already complete","path":"/business-made/onboarding/{employeeId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/onboarding/{employeeId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · People"],"description":"Abandons onboarding — a hire who never started. The record is kept, so a withdrawn offer is visible rather than vanishing.\n\n#### Signature\n\n```http\nPOST /business-made/onboarding/{employeeId}/cancel (employeeId: string, body) -> The cancelled onboarding record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id or code. | — |\n| `400` | ONBOARDING_CLOSED | Onboarding is already complete | The onboarding is complete (or cancelled: \"Onboarding is cancelled\"). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/onboarding/{employeeId}/complete`","requestBody":{"description":"Why it was cancelled.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Candidate withdrew"}}}}}},"/business-made/bills":{"get":{"operationId":"BillController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"example":"approved"},{"name":"vendorId","in":"query","required":false,"schema":{"type":"string"},"example":"VEN-4821"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"},{"name":"page","in":"query","required":false,"schema":{"type":"integer"},"example":1},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"},"example":100}],"responses":{"200":{"description":"Bills","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"List bills","description":"Supplier bills across the org.\n\n#### Signature\n\n```http\nGET /business-made/bills (status?: string, vendorId?: string, businessLocationId?: string, page?: integer, pageSize?: integer) -> Bills\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/bills/aging`"},"post":{"operationId":"BillController_create","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"vendorId, businessLocationId, billDate, dueDate required — Any of the four required fields is absent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"vendorId, businessLocationId, billDate, dueDate required","path":"/business-made/bills","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Create a bill","description":"Records a supplier bill. `vendorId`, `businessLocationId`, `billDate` and `dueDate` are all required — the due date is what drives the aging report.\n\nMoney is in **currency units, not cents**: a $412.50 bill is `amount: 412.5`. Send `lines[]` ({ description, quantity, unitPrice }) for an itemised bill, or just `amount` for a one-line bill — the total and balance are computed from the lines.\n\n#### Signature\n\n```http\nPOST /business-made/bills (body) -> The created bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | vendorId, businessLocationId, billDate, dueDate required | Any of the four required fields is absent. | All four are mandatory — the message lists them. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/approve`","requestBody":{"description":"The bill to record.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"vendorId":"VEN-4821","businessLocationId":"loc_london","billDate":"2026-09-01","dueDate":"2026-10-01","amount":412.5,"reference":"INV-99182","description":"Produce"}}}}}},"/business-made/bills/aging":{"get":{"operationId":"BillController_aging","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one location.","example":"loc_london"}],"responses":{"200":{"description":"Aged payables","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Get bill aging","description":"Payables bucketed by how overdue they are — what is owed, and how late.\n\n#### Signature\n\n```http\nGET /business-made/bills/aging (businessLocationId?: string) -> Aged payables\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/pay`"}},"/business-made/bills/payments":{"get":{"operationId":"BillController_listPayments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"billId","required":false,"in":"query","schema":{"type":"string"},"description":"Only payments against this bill."}],"responses":{"200":{"description":"Bill payments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"List bill payments","description":"Payments made against supplier bills.\n\n#### Signature\n\n```http\nGET /business-made/bills/payments (billId?: string) -> Bill payments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/pay`"}},"/business-made/bills/{id}":{"get":{"operationId":"BillController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Get a bill","description":"Fetches one bill with its lines and payment history.\n\n#### Signature\n\n```http\nGET /business-made/bills/{id} (id: string) -> The bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/approve`"},"delete":{"operationId":"BillController_remove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Delete a bill","description":"Deletes a bill. Void it instead once it has been approved — the record of an approved obligation should survive.\n\n#### Signature\n\n```http\nDELETE /business-made/bills/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/void`"}},"/business-made/bills/update":{"post":{"operationId":"BillController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"sk (the bill id) is required — The body has no `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"sk (the bill id) is required","path":"/business-made/bills/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bill not found — No bill has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bill not found","path":"/business-made/bills/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Update a bill","description":"Updates a bill. Approving and paying have their own endpoints that enforce the state rules.\n\n#### Signature\n\n```http\nPOST /business-made/bills/update (body) -> The updated bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | sk (the bill id) is required | The body has no `sk`. | — |\n| `404` | — | Bill not found | No bill has that id. | Check the id with `GET /business-made/bills`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/approve`","requestBody":{"description":"The bill to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"BIL-4821","dueDate":"2026-10-15"}}}}}},"/business-made/bills/{id}/approve":{"post":{"operationId":"BillController_approve","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The approved bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot approve bill in status <status> — The bill is voided or already approved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot approve bill in status <status>","path":"/business-made/bills/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bill not found — No bill has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bill not found","path":"/business-made/bills/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Approve a bill","description":"Approves a bill for payment. Only a bill in an approvable state can be approved — the error names the status that blocked it.\n\n#### Signature\n\n```http\nPOST /business-made/bills/{id}/approve (id: string) -> The approved bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Bill not found | No bill has that id. | Check the id with `GET /business-made/bills`. |\n| `400` | INVALID_STATUS | Cannot approve bill in status <status> | The bill is voided or already approved. | The message names the current status. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/pay`"}},"/business-made/bills/{id}/void":{"post":{"operationId":"BillController_void_","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The voided bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bill not found — No bill has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bill not found","path":"/business-made/bills/{id}/void","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Void a bill","description":"Voids a bill so it cannot be paid, keeping the record. The right response to a supplier invoice raised in error.\n\n#### Signature\n\n```http\nPOST /business-made/bills/{id}/void (id: string, body) -> The voided bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Bill not found | No bill has that id. | Check the id with `GET /business-made/bills`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills/{id}/pay`","requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Duplicate of INV-99181"}}}}}},"/business-made/bills/{id}/pay":{"post":{"operationId":"BillController_pay","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The updated bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot pay voided bill — The bill has been voided.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot pay voided bill","path":"/business-made/bills/{id}/pay","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bill not found — No bill has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bill not found","path":"/business-made/bills/{id}/pay","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Pay a bill","description":"Records a payment against a bill. Partial payments are supported — call it repeatedly until the balance clears.\n\nTwo guards apply: a voided bill cannot be paid, and a payment exceeding the remaining balance is refused with both figures in the message.\n\n#### Signature\n\n```http\nPOST /business-made/bills/{id}/pay (id: string, body) -> The updated bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent — a retry records a second payment.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Bill not found | No bill has that id. | Check the id with `GET /business-made/bills`. |\n| `400` | — | Cannot pay voided bill | The bill has been voided. | Raise a new bill if the obligation is real. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/bills/payments`","requestBody":{"description":"The payment.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"amount":1200,"method":"bank_transfer","reference":"BACS-99182","date":"2026-09-25"}}}}}},"/business-made/bills/../vendors":{"get":{"operationId":"BillController_listVendors","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Vendors, if the route can be reached at all","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"List vendors (broken route)","description":"Intended to list vendors, but **the route path is malformed**. It is declared as `@Get('../vendors')` inside the bills controller, and a route decorator is not a filesystem path — the `..` is taken literally, so the endpoint registers at `/business-made/bills/../vendors`.\n\nMost HTTP clients normalise `..` out of a URL before sending it, which means a request for that path usually arrives as `/business-made/vendors` and never reaches this handler at all. Treat the endpoint as unreachable and use `GET /business-made/vendors`, which is the properly-mounted route on the vendor controller.\n\n#### Signature\n\n```http\nGET /business-made/bills/../vendors () -> Vendors, if the route can be reached at all\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The `..` is a defect in the route declaration, not a path shorthand.\n- Use `GET /business-made/vendors` instead.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/vendors`"}},"/business-made/vendors":{"get":{"operationId":"VendorController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Vendors","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"List vendors","description":"Suppliers the org buys from.\n\n#### Signature\n\n```http\nGET /business-made/vendors () -> Vendors\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bills`"},"post":{"operationId":"VendorController_create","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created vendor","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Create a vendor","description":"Adds a supplier. Bank details recorded here are where payments go — treat a change to them as a security event, not a routine edit.\n\n#### Signature\n\n```http\nPOST /business-made/vendors (body) -> The created vendor\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/vendors/update`","requestBody":{"description":"The vendor to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Acme Supplies Ltd","paymentTermsDays":30,"email":"ap@acmesupplies.com"}}}}}},"/business-made/vendors/{id}":{"get":{"operationId":"VendorController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The vendor","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Get a vendor","description":"Fetches one vendor with its terms and contact details.\n\n#### Signature\n\n```http\nGET /business-made/vendors/{id} (id: string) -> The vendor\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/bills`"},"delete":{"operationId":"VendorController_remove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Delete a vendor","description":"Deletes a supplier. Bills referencing it are not removed.\n\n#### Signature\n\n```http\nDELETE /business-made/vendors/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/bills`"}},"/business-made/vendors/update":{"post":{"operationId":"VendorController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated vendor","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"sk (the vendor id) is required — The body has no `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"sk (the vendor id) is required","path":"/business-made/vendors/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Vendor not found — No vendor has that `sk`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Vendor not found","path":"/business-made/vendors/update","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Bills"],"summary":"Update a vendor","description":"Updates a supplier record. Changing bank details is the classic invoice-fraud vector — verify the request out of band before applying it.\n\n#### Signature\n\n```http\nPOST /business-made/vendors/update (body) -> The updated vendor\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Verify bank detail changes through a known contact, not the request that asked for them.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | sk (the vendor id) is required | The body has no `sk`. | — |\n| `404` | — | Vendor not found | No vendor has that `sk`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/vendors/{id}`","requestBody":{"description":"The vendor to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"VEN-4821","paymentTermsDays":45}}}}}},"/business-made/books/accounts":{"get":{"operationId":"BooksController_listAccounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Accounts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"List accounts","description":"The chart of accounts — every ledger account and its type.\n\n#### Signature\n\n```http\nGET /business-made/books/accounts () -> Accounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/ledger/{accountCode}`"},"post":{"operationId":"BooksController_createAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Create an account","description":"Adds an account to the chart. Its type determines which side of the balance sheet it falls on, so it cannot be sensibly changed once entries are posted to it.\n\n#### Signature\n\n```http\nPOST /business-made/books/accounts (body) -> The created account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/accounts/update`","requestBody":{"description":"The account to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"5100","name":"Cost of goods sold","type":"expense"}}}}}},"/business-made/books/accounts/{id}":{"get":{"operationId":"BooksController_getAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get an account","description":"Fetches one ledger account.\n\n#### Signature\n\n```http\nGET /business-made/books/accounts/{id} (id: string) -> The account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/ledger/{accountCode}`"}},"/business-made/books/accounts/update":{"post":{"operationId":"BooksController_updateAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Update an account","description":"Updates an account. Renaming is safe; changing its `type` after entries exist changes how every historical report treats it.\n\n#### Signature\n\n```http\nPOST /business-made/books/accounts/update (body) -> The updated account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Avoid changing `type` on an account that already has postings.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/trial-balance`","requestBody":{"description":"The account to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"ACC-5100","name":"Cost of sales"}}}}}},"/business-made/books/journals":{"get":{"operationId":"BooksController_listJournals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"example":"posted"},{"name":"page","in":"query","required":false,"schema":{"type":"integer"},"example":1},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"},"example":100}],"responses":{"200":{"description":"Journals","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"List journals","description":"Journal entries, drafted and posted.\n\n#### Signature\n\n```http\nGET /business-made/books/journals (businessLocationId?: string, status?: string, page?: integer, pageSize?: integer) -> Journals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No date filter is applied: `from`/`to` are not read.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/journals/{id}/post`"},"post":{"operationId":"BooksController_createJournal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The draft journal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Create a journal","description":"Creates a journal entry as a **draft**. Nothing reaches the ledger and no report moves until it is posted.\n\nEntries are double-entry: debits and credits must balance.\n\n#### Signature\n\n```http\nPOST /business-made/books/journals (body) -> The draft journal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Drafting affects nothing — `post` is the step that moves the books.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/journals/{id}/post`","requestBody":{"description":"The journal to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"date":"2026-09-30","memo":"Accrue September rent","lines":[{"accountCode":"6100","debit":4500},{"accountCode":"2100","credit":4500}]}}}}}},"/business-made/books/journals/{id}":{"get":{"operationId":"BooksController_getJournal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The journal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get a journal","description":"Fetches one journal entry with its lines.\n\n#### Signature\n\n```http\nGET /business-made/books/journals/{id} (id: string) -> The journal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/journals/{id}/post`"}},"/business-made/books/journals/{id}/post":{"post":{"operationId":"BooksController_postJournal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The posted journal","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Post a journal","description":"Posts a draft journal to the ledger. **This moves the books** — balances change and reports for the period shift.\n\nCorrecting a posted entry means a reversing journal, not an edit, which is what keeps the ledger auditable.\n\n#### Signature\n\n```http\nPOST /business-made/books/journals/{id}/post (id: string) -> The posted journal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Correct by reversing, never by editing a posted entry.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/trial-balance`"}},"/business-made/books/ledger/{accountCode}":{"get":{"operationId":"BooksController_ledger","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountCode","required":true,"in":"path","schema":{"type":"string"},"description":"Account code (prefix match).","example":"5100"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"{ accountCode, lines: [{ date, ref, memo, businessLocationId, debit, credit, running }], totalDebit, totalCredit, endingBalance }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get an account ledger","description":"Every line posted against an account (posted or reconciled journals; the code matches by prefix, so `1110` also matches `1110 Operating`), with a running balance — the drill-down behind a figure on a report. Reads up to 1,000 journals.\n\n#### Signature\n\n```http\nGET /business-made/books/ledger/{accountCode} (accountCode: string, businessLocationId?: string) -> { accountCode, lines: [{ date, ref, memo, businessLocationId, debit, credit, running }], totalDebit, totalCredit, endingBalance }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No date filter is applied: `from`/`to` are not read.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/trial-balance`"}},"/business-made/books/ar":{"get":{"operationId":"BooksController_ar","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"Accounts receivable","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get accounts receivable","description":"What customers owe, optionally by location — the receivables position from the ledger rather than from invoices.\n\n#### Signature\n\n```http\nGET /business-made/books/ar (businessLocationId?: string) -> Accounts receivable\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/bills/aging`"}},"/business-made/books/overview":{"get":{"operationId":"BooksController_overview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one location.","example":"loc_london"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-30"}],"responses":{"200":{"description":"Books overview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get a books overview","description":"Headline financial position — the summary a dashboard reads.\n\n#### Signature\n\n```http\nGET /business-made/books/overview (businessLocationId?: string, fromDate?: string, toDate?: string) -> Books overview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/pl`"}},"/business-made/books/reports/pl":{"get":{"operationId":"BooksController_pl","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","in":"query","required":false,"description":"Start of the period (YYYY-MM-DD). Also accepted as `fromDate` or `startDate`. Default: the first of this month.","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"End of the period. Also accepted as `toDate` or `endDate`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"},{"name":"groupByLocation","in":"query","required":false,"description":"Split each line by location.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The P&L report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"fromDate \"<value>\" is not a date — use YYYY-MM-DD — A period or as-of date does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"fromDate \"<value>\" is not a date — use YYYY-MM-DD","path":"/business-made/books/reports/pl","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get the profit and loss report","description":"Income and expenses for a period. Figures include only **posted** journals — a draft entry is invisible here.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/pl (from?: string, to?: string, businessLocationId?: string, groupByLocation?: boolean) -> The P&L report\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | fromDate \"<value>\" is not a date — use YYYY-MM-DD | A period or as-of date does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/pl/pdf`"}},"/business-made/books/reports/balance-sheet":{"get":{"operationId":"BooksController_bs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"asOfDate","in":"query","required":false,"description":"The date the report is taken at (YYYY-MM-DD). Also accepted as `asOf` or `date`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"The balance sheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"fromDate \"<value>\" is not a date — use YYYY-MM-DD — A period or as-of date does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"fromDate \"<value>\" is not a date — use YYYY-MM-DD","path":"/business-made/books/reports/balance-sheet","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get the balance sheet","description":"Assets, liabilities and equity at a point in time.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/balance-sheet (asOfDate?: string, businessLocationId?: string) -> The balance sheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | fromDate \"<value>\" is not a date — use YYYY-MM-DD | A period or as-of date does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/balance-sheet/pdf`"}},"/business-made/books/reports/cash-flow":{"get":{"operationId":"BooksController_cf","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","in":"query","required":false,"description":"Start of the period (YYYY-MM-DD). Also accepted as `fromDate` or `startDate`. Default: the first of this month.","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"End of the period. Also accepted as `toDate` or `endDate`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"The cash flow report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"fromDate \"<value>\" is not a date — use YYYY-MM-DD — A period or as-of date does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"fromDate \"<value>\" is not a date — use YYYY-MM-DD","path":"/business-made/books/reports/cash-flow","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get the cash flow report","description":"Cash movement for a period — distinct from profit, which is what makes it worth reading separately.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/cash-flow (from?: string, to?: string, businessLocationId?: string) -> The cash flow report\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | fromDate \"<value>\" is not a date — use YYYY-MM-DD | A period or as-of date does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/pl`"}},"/business-made/books/reports/close-check":{"get":{"operationId":"BooksController_closeCheck","summary":"Run month-end close checks","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","in":"query","required":false,"description":"Start of the period (YYYY-MM-DD). Also accepted as `fromDate` or `startDate`. Default: the first of this month.","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"End of the period. Also accepted as `toDate` or `endDate`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"Close check results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Runs the checks that should pass before a period is closed — unbalanced entries, unposted drafts and accounts that look wrong.\n\nRun this before locking a period: once locked, fixing anything means reopening, which is visible in the audit trail.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/close-check (from?: string, to?: string, businessLocationId?: string) -> Close check results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/period-close/{id}/lock`"}},"/business-made/books/reports/trial-balance":{"get":{"operationId":"BooksController_tb","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"asOfDate","in":"query","required":false,"description":"The date the report is taken at (YYYY-MM-DD). Also accepted as `asOf` or `date`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"The trial balance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"fromDate \"<value>\" is not a date — use YYYY-MM-DD — A period or as-of date does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"fromDate \"<value>\" is not a date — use YYYY-MM-DD","path":"/business-made/books/reports/trial-balance","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get the trial balance","description":"Every account with its debit and credit totals. If it does not balance, something is wrong at the ledger level — check this before closing a period.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/trial-balance (asOfDate?: string, businessLocationId?: string) -> The trial balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | fromDate \"<value>\" is not a date — use YYYY-MM-DD | A period or as-of date does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/close-check`"}},"/business-made/books/reports/by-location":{"get":{"operationId":"BooksController_perLocation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","in":"query","required":false,"description":"Start of the period (YYYY-MM-DD). Also accepted as `fromDate` or `startDate`. Default: the first of this month.","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"End of the period. Also accepted as `toDate` or `endDate`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"}],"responses":{"200":{"description":"Per-location figures","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"fromDate \"<value>\" is not a date — use YYYY-MM-DD — A period or as-of date does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"fromDate \"<value>\" is not a date — use YYYY-MM-DD","path":"/business-made/books/reports/by-location","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"summary":"Get financials by location","description":"Financial performance split by business location — where a multi-site operation is actually making or losing money.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/by-location (from?: string, to?: string) -> Per-location figures\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | fromDate \"<value>\" is not a date — use YYYY-MM-DD | A period or as-of date does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/pl`"}},"/business-made/books/reports/pl/pdf":{"get":{"operationId":"BooksController_plPdf","summary":"Download the P&L as PDF","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","in":"query","required":false,"description":"Start of the period (YYYY-MM-DD). Also accepted as `fromDate` or `startDate`. Default: the first of this month.","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"End of the period. Also accepted as `toDate` or `endDate`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"The P&L PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"fromDate \"<value>\" is not a date — use YYYY-MM-DD — A period or as-of date does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"fromDate \"<value>\" is not a date — use YYYY-MM-DD","path":"/business-made/books/reports/pl/pdf","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"The profit and loss report rendered as a PDF.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/pl/pdf (from?: string, to?: string, businessLocationId?: string) -> The P&L PDF\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | fromDate \"<value>\" is not a date — use YYYY-MM-DD | A period or as-of date does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/pl`"}},"/business-made/books/reports/balance-sheet/pdf":{"get":{"operationId":"BooksController_bsPdf","summary":"Download the balance sheet as PDF","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"asOfDate","in":"query","required":false,"description":"The date the report is taken at (YYYY-MM-DD). Also accepted as `asOf` or `date`. Default: today.","schema":{"type":"string"},"example":"2026-09-30"},{"name":"businessLocationId","in":"query","required":false,"description":"Restrict to one location.","schema":{"type":"string"},"example":"loc_london"}],"responses":{"200":{"description":"The balance sheet PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"fromDate \"<value>\" is not a date — use YYYY-MM-DD — A period or as-of date does not parse.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"fromDate \"<value>\" is not a date — use YYYY-MM-DD","path":"/business-made/books/reports/balance-sheet/pdf","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"The balance sheet rendered as a PDF.\n\n#### Signature\n\n```http\nGET /business-made/books/reports/balance-sheet/pdf (asOfDate?: string, businessLocationId?: string) -> The balance sheet PDF\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | fromDate \"<value>\" is not a date — use YYYY-MM-DD | A period or as-of date does not parse. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/balance-sheet`"}},"/business-made/period-close":{"get":{"operationId":"PeriodCloseController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Period closes","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"List period closes","description":"Accounting periods and their close status.\n\n#### Signature\n\n```http\nGET /business-made/period-close () -> Period closes\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/period-close/start`"}},"/business-made/period-close/{id}":{"get":{"operationId":"PeriodCloseController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The period close","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Get a period close","description":"Fetches one close with its checklist and progress.\n\n#### Signature\n\n```http\nGET /business-made/period-close/{id} (id: string) -> The period close\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/period-close/{id}/lock`"}},"/business-made/period-close/start":{"post":{"operationId":"PeriodCloseController_start","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The started close","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Start a period close","description":"Opens the close process for a period, generating its checklist. Run the close checks first so the checklist starts from a clean position.\n\n#### Signature\n\n```http\nPOST /business-made/period-close/start (body) -> The started close\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/close-check`","requestBody":{"description":"The period to close.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"periodStart":"2026-09-01","periodEnd":"2026-09-30"}}}}}},"/business-made/period-close/{id}/checklist/{itemId}":{"post":{"operationId":"PeriodCloseController_toggle","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Checklist item id.","example":"CHK-recon-bank"}],"responses":{"201":{"description":"The updated close","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Close not found — No period close has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Close not found","path":"/business-made/period-close/{id}/checklist/{itemId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Complete a checklist item","description":"Marks one close checklist item done. Required items must all be complete before the period can be locked.\n\n#### Signature\n\n```http\nPOST /business-made/period-close/{id}/checklist/{itemId} (id: string, itemId: string, body) -> The updated close\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Close not found | No period close has that id. | List them with `GET /business-made/period-close`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/period-close/{id}/lock`","requestBody":{"description":"Optional completion note.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Bank reconciled to statement 09/30"}}}}}},"/business-made/period-close/{id}/lock":{"post":{"operationId":"PeriodCloseController_lock","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The locked period","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"<n> required item(s) incomplete — Required checklist items remain outstanding.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"<n> required item(s) incomplete","path":"/business-made/period-close/{id}/lock","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Close not found — No period close has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Close not found","path":"/business-made/period-close/{id}/lock","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Lock a period","description":"Closes and locks an accounting period so no further entries can be posted into it. The point the reports for that period stop moving.\n\n**Every required checklist item must be complete** — the error reports how many are outstanding and which they are, rather than simply refusing.\n\n#### Signature\n\n```http\nPOST /business-made/period-close/{id}/lock (id: string) -> The locked period\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Reopening a locked period is visible in the audit trail — get the checks right first.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Close not found | No period close has that id. | List them with `GET /business-made/period-close`. |\n| `400` | — | <n> required item(s) incomplete | Required checklist items remain outstanding. | The body lists the incomplete items. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/period-close/{id}/reopen`"}},"/business-made/period-close/{id}/reopen":{"post":{"operationId":"PeriodCloseController_reopen","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The reopened period","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Close not found — No period close has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Close not found","path":"/business-made/period-close/{id}/reopen","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Reopen a period","description":"Unlocks a closed period so entries can be posted into it again. Reopening after reports have been filed or shared means those figures may now change — record why.\n\n#### Signature\n\n```http\nPOST /business-made/period-close/{id}/reopen (id: string, body) -> The reopened period\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Anything already reported from the period may no longer match.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Close not found | No period close has that id. | List them with `GET /business-made/period-close`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/period-close/{id}/lock`","requestBody":{"description":"Why it was reopened.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Late supplier invoice for September"}}}}}},"/business-made/budgets":{"get":{"operationId":"BudgetController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Budgets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"List budgets","description":"Budgets defined for the org.\n\n#### Signature\n\n```http\nGET /business-made/budgets () -> Budgets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/budgets/{id}/variance`"},"post":{"operationId":"BudgetController_create","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created budget","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Create a budget","description":"Creates a budget for a period, by account or department.\n\n#### Signature\n\n```http\nPOST /business-made/budgets (body) -> The created budget\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/budgets/{id}/variance`","requestBody":{"description":"The budget to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"FY2026 engineering","periodStart":"2026-01-01","periodEnd":"2026-12-31","lines":[{"accountCode":"6100","amount":240000}]}}}}}},"/business-made/budgets/{id}":{"get":{"operationId":"BudgetController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The budget","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Get a budget","description":"Fetches one budget with its lines.\n\n#### Signature\n\n```http\nGET /business-made/budgets/{id} (id: string) -> The budget\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/budgets/{id}/variance`"}},"/business-made/budgets/update":{"post":{"operationId":"BudgetController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated budget","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Update a budget","description":"Updates a budget. Revising it mid-period changes the variance figures retrospectively — keep a record of the original if the comparison matters.\n\n#### Signature\n\n```http\nPOST /business-made/budgets/update (body) -> The updated budget\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/budgets/{id}/variance`","requestBody":{"description":"The budget to update.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"id":"BUD-4821","lines":[{"accountCode":"6100","amount":260000}]}}}}}},"/business-made/budgets/{id}/variance":{"get":{"operationId":"BudgetController_variance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Budget variance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Budget not found — No budget has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Budget not found","path":"/business-made/budgets/{id}/variance","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Period close"],"summary":"Get budget variance","description":"Budget against actual for a period — where spending has diverged from plan. Actuals come from posted journals, so unposted entries do not appear.\n\n#### Signature\n\n```http\nGET /business-made/budgets/{id}/variance (id: string) -> Budget variance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Budget not found | No budget has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/pl`"}},"/business-made/setup/demo-hr-payroll":{"post":{"operationId":"SetupController_seedDemoHrPayroll","summary":"Seed demo HR and payroll data","description":"Idempotent: ensures a demo@appmint.io user with a linked employee, payroll config, a payroll profile, three full payroll runs (calculate → approve → process → pay stubs) and recruitment data.\n\n**Never run this against a production org.** It writes fictional people into the same collections real employees live in, and they appear in headcount, payroll runs and reports.\n\n#### Signature\n\n```http\nPOST /business-made/setup/demo-hr-payroll () -> The seeded data\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n- Demo data is indistinguishable from real data once created.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /business-made/setup/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The seeded data","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"]}},"/business-made/setup":{"get":{"operationId":"SetupController_health","summary":"Health check","parameters":[],"responses":{"200":{"description":"Health","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Answers `{ status: \"ok\", service: \"business-made\", time }`.\n\n#### Signature\n\n```http\nGET /business-made/setup () -> Health\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`."}},"/business-made/setup/locations":{"post":{"operationId":"SetupController_seedLocations","summary":"Seed demo locations","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Creates demo business locations.\n\n#### Signature\n\n```http\nPOST /business-made/setup/locations () -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`."}},"/business-made/setup/vendors":{"post":{"operationId":"SetupController_seedVendors","summary":"Seed vendors","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Creates default vendor records.\n\n#### Signature\n\n```http\nPOST /business-made/setup/vendors () -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /business-made/vendors`"}},"/business-made/setup/chart-of-accounts":{"post":{"operationId":"SetupController_seedCoA","summary":"Seed the chart of accounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Creates a standard chart of accounts. Get this right before posting anything — restructuring accounts after entries exist is considerably harder.\n\n#### Signature\n\n```http\nPOST /business-made/setup/chart-of-accounts () -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/accounts`"}},"/business-made/setup/product-attributes":{"post":{"operationId":"SetupController_seedProductAttributes","summary":"Seed product attributes","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Creates the `businessLocation` and `prepStation` product attribute definitions.\n\n#### Signature\n\n```http\nPOST /business-made/setup/product-attributes () -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`."}},"/business-made/setup/workflows":{"post":{"operationId":"SetupController_seedWorkflows","summary":"Seed the canonical workflows","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Creates every canonical workflow template (prep, reservation-checkin, pickup, application-processing, service-appointment, renewal).\n\n#### Signature\n\n```http\nPOST /business-made/setup/workflows () -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /business-made/setup/workflows/{name}`"}},"/business-made/setup/workflows/{name}":{"post":{"operationId":"SetupController_seedOneWorkflow","summary":"Seed one canonical workflow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string","enum":["prep","reservation-checkin","pickup","application-processing","service-appointment","renewal"]},"description":"Workflow.","example":"pickup"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Creates one canonical workflow template.\n\n#### Signature\n\n```http\nPOST /business-made/setup/workflows/{name} (name: string) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`."}},"/business-made/setup/all":{"post":{"operationId":"SetupController_seedAll","summary":"Seed everything","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Seeds locations, vendors, the chart of accounts, product attributes and the canonical workflows in one call.\n\n#### Signature\n\n```http\nPOST /business-made/setup/all () -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /business-made/setup/status`"}},"/business-made/setup/status":{"get":{"operationId":"SetupController_getStatus","summary":"Get setup status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ profile, scenarios: { <key>: { completedAt, completedBy, summary } }, modules: { … } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Company <orgId> not found in Root Org — The `orgid` header names no organization.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Company <orgId> not found in Root Org","path":"/business-made/setup/status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"The org's setup progress: its business profile, and which scenarios and modules have been applied and when.\n\n#### Signature\n\n```http\nGET /business-made/setup/status () -> { profile, scenarios: { <key>: { completedAt, completedBy, summary } }, modules: { … } }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Company <orgId> not found in Root Org | The `orgid` header names no organization. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /business-made/setup/profile/save`"}},"/business-made/setup/profile/save":{"post":{"operationId":"SetupController_saveProfile","summary":"Save the business profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["businessName"],"properties":{"businessName":{"type":"string"},"legalName":{"type":"string"},"taxId":{"type":"string"},"industry":{"type":"string"},"fiscalYearStart":{"type":"string","description":"MM-DD"},"currency":{"type":"string"},"timezone":{"type":"string"},"logoUrl":{"type":"string"},"address":{"type":"object","additionalProperties":true},"contactEmail":{"type":"string"},"contactPhone":{"type":"string"}}},"example":{"businessName":"Harbor Coffee","industry":"cafe","fiscalYearStart":"01-01","currency":"USD","timezone":"America/New_York"}}}},"responses":{"201":{"description":"The setup status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"businessName is required — `businessName` is missing or blank.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"businessName is required","path":"/business-made/setup/profile/save","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Company <orgId> not found in Root Org — The `orgid` header names no organization.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Company <orgId> not found in Root Org","path":"/business-made/setup/profile/save","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Saves the org's business profile — the first setup step, completed before scenarios and modules are applied.\n\n#### Signature\n\n```http\nPOST /business-made/setup/profile/save (body) -> The setup status\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | businessName is required | `businessName` is missing or blank. | — |\n| `404` | — | Company <orgId> not found in Root Org | The `orgid` header names no organization. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /business-made/setup/scenario/apply`"}},"/business-made/setup/scenario/apply":{"post":{"operationId":"SetupController_applyScenario","summary":"Apply a setup scenario","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scenario"],"properties":{"scenario":{"type":"string","enum":["restaurant","cafe","bar","retail","services","salon","hospitality","professional-services","generic"]}}},"example":{"scenario":"cafe"}}}},"responses":{"201":{"description":"{ status, summary: { <module>: { created, skipped, details? } } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown scenario: <scenario> — `scenario` is not one of the listed keys.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown scenario: <scenario>","path":"/business-made/setup/scenario/apply","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Company <orgId> not found in Root Org — The `orgid` header names no organization.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Company <orgId> not found in Root Org","path":"/business-made/setup/scenario/apply","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Applies a whole business shape at once: runs every module the scenario bundles, creating their default records, and marks the scenario complete.\n\n#### Signature\n\n```http\nPOST /business-made/setup/scenario/apply (body) -> { status, summary: { <module>: { created, skipped, details? } } }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Unknown scenario: <scenario> | `scenario` is not one of the listed keys. | — |\n| `404` | — | Company <orgId> not found in Root Org | The `orgid` header names no organization. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/business-made/setup/module/apply":{"post":{"operationId":"SetupController_applyModule","summary":"Apply one setup module","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["module"],"properties":{"module":{"type":"string","enum":["books","hr","payroll","time","operations","workflows","crm","storefront","content","automation","analytics"]}}},"example":{"module":"payroll"}}}},"responses":{"201":{"description":"{ status, summary: { created, skipped, details? } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Unknown module: <module> — `module` is not one of the listed keys.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown module: <module>","path":"/business-made/setup/module/apply","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Company <orgId> not found in Root Org — The `orgid` header names no organization.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Company <orgId> not found in Root Org","path":"/business-made/setup/module/apply","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Setup"],"description":"Creates the default records one module needs, à la carte, and marks the module complete.\n\n#### Signature\n\n```http\nPOST /business-made/setup/module/apply (body) -> { status, summary: { created, skipped, details? } }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public route: no sign-in is checked — the `orgid` header alone chooses the org.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Unknown module: <module> | `module` is not one of the listed keys. | — |\n| `404` | — | Company <orgId> not found in Root Org | The `orgid` header names no organization. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/business-made/payroll/earning-types":{"get":{"operationId":"PayrollConfigController_listEarningTypes","summary":"List earning types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Earning types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"The kinds of pay the org recognises — salary, hourly, overtime, bonus, commission. What a pay stub line can be.\n\n#### Signature\n\n```http\nGET /business-made/payroll/earning-types () -> Earning types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/earning-types/seed`"},"post":{"operationId":"PayrollConfigController_createEarningType","summary":"Create an earning type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created earning type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Defines a kind of pay. Its tax treatment determines how the amount is taxed, so an incorrectly configured type misstates withholding on every stub that uses it.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/earning-types (body) -> The created earning type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/earning-types/seed`","requestBody":{"description":"The earning type to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"OT","name":"Overtime","taxable":true,"multiplier":1.5}}}}}},"/business-made/payroll/earning-types/{id}":{"get":{"operationId":"PayrollConfigController_getEarningType","summary":"Get an earning type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The earning type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Fetches one earning type with its tax treatment.\n\n#### Signature\n\n```http\nGET /business-made/payroll/earning-types/{id} (id: string) -> The earning type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /business-made/payroll/earning-types/{id}`"},"patch":{"operationId":"PayrollConfigController_updateEarningType","summary":"Update an earning type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The updated earning type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Updates an earning type. Applies to future calculations; stubs already produced keep their original treatment.\n\n#### Signature\n\n```http\nPATCH /business-made/payroll/earning-types/{id} (id: string, body) -> The updated earning type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/earning-types`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"multiplier":2}}}}},"delete":{"operationId":"PayrollConfigController_deleteEarningType","summary":"Delete an earning type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Deletes an earning type. Historical stubs referencing it keep their figures but lose the definition behind them.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/earning-types/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/earning-types`"}},"/business-made/payroll/earning-types/seed":{"post":{"operationId":"PayrollConfigController_seedEarningTypes","summary":"Seed standard earning types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The seeded earning types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Creates the standard set of earning types for a new org — the fast path to a working payroll setup. Idempotent, so it is safe to re-run.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/earning-types/seed () -> The seeded earning types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/deduction-types/seed`"}},"/business-made/payroll/deduction-types":{"get":{"operationId":"PayrollConfigController_listDeductionTypes","summary":"List deduction types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Deduction types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"The kinds of deduction the org recognises — pension, health premium, garnishment, loan repayment.\n\n#### Signature\n\n```http\nGET /business-made/payroll/deduction-types () -> Deduction types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/employee-deductions`"},"post":{"operationId":"PayrollConfigController_createDeductionType","summary":"Create a deduction type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created deduction type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Defines a kind of deduction. **Whether it is pre-tax changes the taxable base**, so this setting affects tax withheld on every stub it appears on — not just the deduction line.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/deduction-types (body) -> The created deduction type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `preTax` is the field most likely to be wrong and hardest to spot afterwards.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/deduction-types/seed`","requestBody":{"description":"The deduction type to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"PENSION","name":"Pension contribution","preTax":true,"annualLimit":23000}}}}}},"/business-made/payroll/deduction-types/{id}":{"get":{"operationId":"PayrollConfigController_getDeductionType","summary":"Get a deduction type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The deduction type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Fetches one deduction type with its pre-tax treatment and limits.\n\n#### Signature\n\n```http\nGET /business-made/payroll/deduction-types/{id} (id: string) -> The deduction type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /business-made/payroll/deduction-types/{id}`"},"patch":{"operationId":"PayrollConfigController_updateDeductionType","summary":"Update a deduction type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The updated deduction type","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Updates a deduction type. Changing `preTax` alters withholding on future runs — reconcile the year-to-date figures afterwards.\n\n#### Signature\n\n```http\nPATCH /business-made/payroll/deduction-types/{id} (id: string, body) -> The updated deduction type\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/deduction-types`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"annualLimit":24000}}}}},"delete":{"operationId":"PayrollConfigController_deleteDeductionType","summary":"Delete a deduction type","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Deletes a deduction type. Employee deductions referencing it are not removed — cancel those first.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/deduction-types/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employee-deductions/{id}/cancel`"}},"/business-made/payroll/deduction-types/seed":{"post":{"operationId":"PayrollConfigController_seedDeductionTypes","summary":"Seed standard deduction types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The seeded deduction types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Creates the standard deduction types for a new org. Idempotent.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/deduction-types/seed () -> The seeded deduction types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/earning-types/seed`"}},"/business-made/payroll/employee-deductions":{"get":{"operationId":"PayrollConfigController_listEmployeeDeductions","summary":"List employee deductions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":false,"in":"query","schema":{"type":"string"},"example":"EMP-4821"}],"responses":{"200":{"description":"Employee deductions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Deductions assigned to employees across the org, with their status.\n\n#### Signature\n\n```http\nGET /business-made/payroll/employee-deductions (employeeId?: string) -> Employee deductions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employee-deductions`"},"post":{"operationId":"PayrollConfigController_createEmployeeDeduction","summary":"Assign a deduction to an employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The assigned deduction","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Assigns a recurring deduction. It applies from the next calculated run — assigning mid-period does not retrospectively deduct from a run already calculated.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/employee-deductions (body) -> The assigned deduction\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employee-deductions/{id}/pause`","requestBody":{"description":"The deduction to assign.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-4821","deductionTypeId":"DT-pension","amount":250,"startDate":"2026-10-01"}}}}}},"/business-made/payroll/employee-deductions/{id}":{"get":{"operationId":"PayrollConfigController_getEmployeeDeduction","summary":"Get an employee deduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The deduction","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Fetches one assigned deduction with its schedule and remaining balance.\n\n#### Signature\n\n```http\nGET /business-made/payroll/employee-deductions/{id} (id: string) -> The deduction\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /business-made/payroll/employee-deductions/{id}`"},"patch":{"operationId":"PayrollConfigController_updateEmployeeDeduction","summary":"Update an employee deduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The updated deduction","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Updates an assigned deduction. Takes effect on the next calculation.\n\n#### Signature\n\n```http\nPATCH /business-made/payroll/employee-deductions/{id} (id: string, body) -> The updated deduction\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employee-deductions/{id}/pause`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"amount":300}}}}},"delete":{"operationId":"PayrollConfigController_deleteEmployeeDeduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"summary":"Delete an employee deduction","description":"Deletes the assignment outright. Cancel instead where the deduction history matters — for a garnishment it almost always does.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/employee-deductions/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employee-deductions/{id}/cancel`"}},"/business-made/payroll/employee-deductions/{id}/pause":{"post":{"operationId":"PayrollConfigController_pauseEmployeeDeduction","summary":"Pause a deduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The paused deduction","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Suspends a deduction without cancelling it — a contribution holiday, or a garnishment stayed pending review. It stops applying but keeps its history and remaining balance.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/employee-deductions/{id}/pause (id: string) -> The paused deduction\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employee-deductions/{id}/resume`"}},"/business-made/payroll/employee-deductions/{id}/resume":{"post":{"operationId":"PayrollConfigController_resumeEmployeeDeduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The resumed deduction","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"summary":"Resume a deduction","description":"Restarts a paused deduction from the next run. Missed periods are not caught up automatically.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/employee-deductions/{id}/resume (id: string) -> The resumed deduction\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No back-catch. Adjust manually if the missed amounts are owed.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/employee-deductions/{id}/pause`"}},"/business-made/payroll/employee-deductions/{id}/cancel":{"post":{"operationId":"PayrollConfigController_cancelEmployeeDeduction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The cancelled deduction","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"summary":"Cancel a deduction","description":"Ends a deduction permanently, keeping the record of what was deducted. The correct way to stop a loan repayment that has been settled.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/employee-deductions/{id}/cancel (id: string) -> The cancelled deduction\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /business-made/payroll/employee-deductions/{id}`"}},"/business-made/payroll/tax-rules":{"get":{"operationId":"PayrollConfigController_listTaxRules","summary":"List tax rules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Tax rules","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"The tax rules on file — brackets, rates and thresholds by jurisdiction and year.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-rules () -> Tax rules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/coverage`"},"post":{"operationId":"PayrollConfigController_createTaxRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"summary":"Create a tax rule","description":"Creates a single tax rule.\n\nA wrong rule under- or over-withholds from real people and is typically discovered months later by a tax authority. Prefer `seed-us` or `import` for anything beyond a one-off, and verify the year afterwards.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/tax-rules (body) -> The created rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Run `GET /business-made/payroll/tax-rules/verify/{year}` after any manual rule change.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/tax-rules/import`\n- `GET /business-made/payroll/tax-rules/verify/{year}`","requestBody":{"description":"The rule to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"year":2026,"jurisdiction":"US-FED","filingStatus":"single","brackets":[{"upTo":11925,"rate":0.1}]}}}}}},"/business-made/payroll/tax-rules/{id}":{"patch":{"operationId":"PayrollConfigController_updateTaxRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The updated rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"summary":"Update a tax rule","description":"Updates a tax rule. Affects future calculations only — runs already calculated keep the rates they used.\n\n#### Signature\n\n```http\nPATCH /business-made/payroll/tax-rules/{id} (id: string, body) -> The updated rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Recalculate any open run after changing a rule that applies to it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/verify/{year}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"brackets":[{"upTo":12000,"rate":0.1}]}}}}},"delete":{"operationId":"PayrollConfigController_deleteTaxRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"summary":"Delete a tax rule","description":"Deletes a tax rule. Removing one that a jurisdiction still needs leaves a gap the coverage report will flag — check coverage afterwards.\n\n#### Signature\n\n```http\nDELETE /business-made/payroll/tax-rules/{id} (id: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/coverage`"},"get":{"operationId":"PayrollConfigController_getTaxRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The tax rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"summary":"Get a tax rule","description":"Fetches one tax rule.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-rules/{id} (id: string) -> The tax rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /business-made/payroll/tax-rules/{id}`"}},"/business-made/payroll/tax-rules/import":{"post":{"operationId":"PayrollConfigController_importTaxRules","summary":"Import tax rules in bulk","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The rules to import.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["rules"],"properties":{"rules":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The rules. All-or-nothing — one invalid entry rejects the batch."},"replace":{"type":"boolean","default":false,"description":"Replace existing rules for the same scope rather than adding.","example":false}}},"example":{"rules":[{"year":2026,"jurisdiction":"US-CA","filingStatus":"single","brackets":[{"upTo":10756,"rate":0.011}]}],"replace":true}}}},"responses":{"201":{"description":"The import result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"Bulk-imports tax rules — a state table, a full year of federal brackets, anything.\n\n**Every rule is validated before any is written**, so a malformed table is rejected whole rather than leaving half a year of brackets loaded. That is what makes this safe to run against production.\n\nSet `replace` to overwrite the existing rules for the same scope instead of adding to them.\n\n**Certificate-driven withholding.** Besides `flat`, `bracket`, `wage_base_capped`, `additional_threshold` and `none`, a rule may use:\n- `state_withholding` (state_income, or a local with its own schedule like NYC) with a `withholding` block: `method` `annualized_schedule` (`schedules` keyed by name, each with `standardDeduction`, optional `standardDeductionByAllowances`, `lowIncomeExemption`, `brackets`, `wholeIncomeRates`; `scheduleMap` from the certificate's filing status — or `withholdingCode` with `scheduleFrom` — to a schedule; `federalStatusMap` used only when no certificate is on file; wage-keyed look-ups `exemptionByWages`, `addBackByWages`, `recaptureByWages`, `creditRateByWages` (CT); `allowance`/`dependent` `{ mode: deduction|credit, annualAmount }`; `exemptionAmount: { mode }`; `deductionAllowance`; `minimumAnnualWages`; `stateOnlyExemptReasons`; `localityRequired`) or `percent_of_wages` (`allowedPercents`, `defaultPercent`).\n- Local rules (`local_income`, need `locality`): `percent_of_state` (`employeeRate` × the state amount), `percent_of_state_taxable` (flat `employeeRate`, marginal `brackets`, or whole-income `tiers` by schedule, on the state's annual taxable wages), `percent_of_wages` (`employeeRate` on `wageBase` less `annualDeductionPerAllowance` per allowance). Match fields: `localityCode`, `localityName`, `aliases`, `localityKind`, `residentCities`; `followsStateExempt` (default true).\nPublished tables whose accumulated column is rounded may set `flatBaseTolerance` (≤ $5). A table that changes mid-year sets `effectiveFrom` (YYYY-MM-DD) and `effectiveFromBasis` (`pay_date` default, `period_end`, `period_start`).\n\n**Federal (Pub 15-T).** A `federal_income` `bracket` rule may set `method: \"pub15t_percentage\"` with a `percentageMethod` block (`line1gAdjustment`, `allowanceAmount`, `step2CheckboxBrackets`) to compute by IRS Pub 15-T Worksheet 1A; `brackets` is then the STANDARD schedule.\n\n**Disability and family leave.** `state_disability` and `state_family_leave` are employee contributions matched by work state. `wageBasis` (`state` default, `fica`, `gross`) picks the wages the rate applies to; `employeeMaxPerWeek` caps a `flat` rule per week, scaled to the pay frequency.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/tax-rules/import (body) -> The import result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- All-or-nothing. A partial table cannot be created by a failed import.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/verify/{year}`\n- `GET /business-made/payroll/tax-rules/tables/status`"}},"/business-made/payroll/tax-rules/verify/{year}":{"get":{"operationId":"PayrollConfigController_verifyTaxTables","summary":"Verify tax tables for a year","parameters":[{"name":"year","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Worked examples and structural checks","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"Produces a verification sheet for a tax year: worked examples at known incomes plus structural checks on the tables.\n\nThis exists to be checked **against the published source** by a human. Run it after seeding or importing, compare the worked examples to the authority's own tables, and only then run payroll on the year.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-rules/verify/{year} (year: string) -> Worked examples and structural checks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The point is manual review — a clean structural check does not mean the rates are right.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/compare/{fromYear}/{toYear}`"}},"/business-made/payroll/tax-rules/compare/{fromYear}/{toYear}":{"get":{"operationId":"PayrollConfigController_compareTaxTables","summary":"Compare two tax years","parameters":[{"name":"fromYear","required":true,"in":"path","schema":{"type":"string"},"description":"Baseline year.","example":"2025"},{"name":"toYear","required":true,"in":"path","schema":{"type":"string"},"description":"Year to check.","example":"2026"},{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The comparison, with suspicious movements flagged","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"Diffs two years of tax tables and **flags thresholds that moved suspiciously** — the check that catches a transposed digit or a bracket that shifted by an order of magnitude.\n\nYear-on-year thresholds usually move by small inflation adjustments; anything else is worth explaining before you rely on it.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-rules/compare/{fromYear}/{toYear} (fromYear: string, toYear: string) -> The comparison, with suspicious movements flagged\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/verify/{year}`"}},"/business-made/payroll/tax-rules/tables/status":{"get":{"operationId":"PayrollConfigController_taxTableStatus","summary":"Get tax table load status","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Table load status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"Which tax table files loaded successfully and which were rejected. Check this after a deployment — a silently rejected table means calculations fall back to whatever else is on file.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-rules/tables/status () -> Table load status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/tax-rules/import`"}},"/business-made/payroll/tax-rules/coverage":{"get":{"operationId":"PayrollConfigController_taxRuleCoverage","summary":"Get state tax coverage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":false,"in":"query","schema":{"type":"string"},"description":"Tax year to check. Defaults to the current one.","example":"2026"}],"responses":{"200":{"description":"Coverage by state","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"Compares the states employees are actually paid in against the states with rules on file — the gap analysis that catches a new hire in a state nobody has loaded tables for.\n\nA missing state means that employee's state tax cannot be calculated correctly. Run it whenever you hire somewhere new.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-rules/coverage (year?: string) -> Coverage by state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/tax-rules/import`"}},"/business-made/payroll/tax-rules/shadowed":{"get":{"operationId":"PayrollConfigController_shadowedTaxTables","summary":"List org rules that replace an official table","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":false,"in":"query","schema":{"type":"string"},"description":"Run year to check. Defaults to the current year.","example":"2026"}],"responses":{"200":{"description":"Shadowing org rules","content":{"application/json":{"schema":{"type":"object","properties":{"year":{"type":"number"},"shadowed":{"type":"array","items":{"type":"object","properties":{"ruleId":{"type":"string"},"ruleName":{"type":"string"},"jurisdiction":{"type":"string"},"state":{"type":"string"},"locality":{"type":"string"},"officialName":{"type":"string"},"officialYear":{"type":"number"},"certificate":{"type":"string"},"ignored":{"type":"string"},"message":{"type":"string"}}}}}},"example":{"year":2026,"shadowed":[{"ruleId":"66f1c0ffee","ruleName":"Illinois 4.95%","jurisdiction":"state_income","state":"IL","officialName":"IL State Income 2026 (IL-W-4, IL-700-T automated method)","officialYear":2026,"certificate":"IL-W-4","ignored":"allowances on IL-W-4 are ignored","message":"Your rule \"Illinois 4.95%\" replaces the official IL 2026 table (IL State Income 2026 (IL-W-4, IL-700-T automated method)); allowances on IL-W-4 are ignored."}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"Org tax rules that replace a shipped official table for the same jurisdiction, state or locality and year — e.g. a hand-entered flat Illinois rate overriding the IL-W-4-driven IL table, so every allowance an employee claims is ignored. Each row names the official table and, where the table reads a withholding certificate, what the override ignores. A warning only: nothing is changed. Only active rules are checked.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-rules/shadowed (year?: string) -> Shadowing org rules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/tax-rules/{id}/use-official`"}},"/business-made/payroll/tax-rules/{id}/use-official":{"post":{"operationId":"PayrollConfigController_useOfficialTaxTable","summary":"Use the official table instead of an org rule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The retired rule and the table now in force","content":{"application/json":{"schema":{"type":"object","properties":{"retired":{"type":"string"},"officialName":{"type":"string"},"officialYear":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Rule <id> does not replace an official table for <year>, so retiring it would leave nothing in its place. Edit or deactivate it from the rule editor instead. — The rule does not shadow an official table for that year (see `GET /business-made/payroll/tax-rules/shadowed`).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Rule <id> does not replace an official table for <year>, so retiring it would leave nothing in its place. Edit or deactivate it from the rule editor instead.","path":"/business-made/payroll/tax-rules/{id}/use-official","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"description":"Retires an org rule that shadows a shipped official table, so the official table applies. The rule is never deleted: it is set inactive and stamped with `retiredAt`, `retiredBy` and `retiredReason`, and can be re-activated from the rule editor.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/tax-rules/{id}/use-official (id: string, body) -> The retired rule and the table now in force\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | — | Rule <id> does not replace an official table for <year>, so retiring it would leave nothing in its place. Edit or deactivate it from the rule editor instead. | The rule does not shadow an official table for that year (see `GET /business-made/payroll/tax-rules/shadowed`). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/shadowed`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"year":{"type":"number","description":"Run year the shadowing was reported for. Defaults to the current year."}}},"example":{"year":2026}}}}}},"/business-made/payroll/tax-rules/seed-us":{"post":{"operationId":"PayrollConfigController_seedUSRules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The seeded rules","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Tax"],"summary":"Seed US federal tax rules","description":"Loads the standard US federal tax rules for a year. **Idempotent**, so re-running it does not duplicate anything — the intended way to set up a new tax year.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/tax-rules/seed-us (body) -> The seeded rules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/verify/{year}`","requestBody":{"description":"Which year to seed.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"year":{"type":"number","example":2026}}},"example":{"year":2026}}}}}},"/business-made/payroll/run/preview":{"post":{"operationId":"PayrollConfigController_previewRun","summary":"Preview a payroll run","description":"Computes pay stubs **without persisting anything** — no stubs written, no loan balances touched, no year-to-date figures updated.\n\nThis is the dry run. Use it to check figures before `execute`, which is not reversible.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/run/preview (body) -> The computed stubs, unsaved\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Writes nothing. Safe to run as often as you like.\n- Pay readiness: `payReadiness: { exceptions[], held[] }`. Exceptions = gov-form items (missing W-4, I-9 §2 late, re-verification, no pay method, unsigned policy) + requirement rules whose payroll gate is warn/block — each `{ employeeId, name, source: gov_forms|requirement, code, message, fix: { route, label }, effect: warn|block, requirementId?, override? }`. A block holds that employee’s stub only (`held: true`, `holdReasons[]`, a \"Pay held:\" warning); totals exclude held stubs and `totals.held` counts them. The run is never held silently.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/run/execute`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The computed stubs, unsaved","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"requestBody":{"description":"The run to preview.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"payPeriodStart":"2026-09-01","payPeriodEnd":"2026-09-15","employeeIds":["EMP-4821"]}}}}}},"/business-made/payroll/run/execute":{"post":{"operationId":"PayrollConfigController_executeRun","summary":"Execute a payroll run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The executed run and its stubs","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Runs payroll for real: **persists pay stubs, decrements loan balances and updates year-to-date figures**.\n\nAll three side effects matter. Loan balances moving means a repeated execution over-collects; YTD figures moving means the year-end forms shift. Preview first, and do not retry a request whose response was lost without checking what was written.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/run/execute (body) -> The executed run and its stubs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent, and its side effects are cumulative — a duplicate execution double-decrements loans and double-counts YTD.\n- Preview the same input first and compare.\n- Pay readiness is re-checked at persist time: held stubs are not written or paid, the employee is notified (`payroll.held`) and the run records `payReadiness`. Lift a requirement block with `POST business-made/readiness/overrides` (gate payroll) or by fixing it, then pay off-cycle.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/run/preview`","requestBody":{"description":"The run to execute.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"payPeriodStart":"2026-09-01","payPeriodEnd":"2026-09-15"}}}}}},"/business-made/payroll/employees/bulk-import":{"post":{"operationId":"PayrollConfigController_bulkImport","summary":"Bulk import employees with payroll profiles","description":"Imports employees together with their payroll profiles in one operation — the migration path when moving from another provider.\n\nCheck a small batch before importing a whole workforce: pay rates and tax elections arriving wrong here become wrong payslips.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/employees/bulk-import (body) -> The import result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent — re-running an import can duplicate employees.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/run/preview`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The rows to import.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"One row per employee, with their payroll profile fields."}}},"example":{"rows":[{"employeeId":"E-00412","firstName":"Ada","lastName":"Lovelace","annualSalary":76800,"payFrequency":"semi-monthly"}]}}}},"responses":{"201":{"description":"The import result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"]}},"/business-made/payroll/runs/{runId}/ach/generate":{"post":{"operationId":"PayrollConfigController_generateAch","summary":"Generate the ACH file for a run","description":"Produces the NACHA file that instructs the bank to pay everyone in the run.\n\n**This file moves money once submitted to your bank.** Generating it here does not transmit it — but anything downstream that uploads it does, and a file generated twice and submitted twice pays twice.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/{runId}/ach/generate (runId: string) -> The generated ACH file reference\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Generation is not transmission — but treat the file as live payment instructions from the moment it exists.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/runs/{runId}/ach/download`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"runId","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"201":{"description":"The generated ACH file reference","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"]}},"/business-made/payroll/runs/{runId}/ach/download":{"get":{"operationId":"PayrollConfigController_downloadAch","summary":"Download the ACH file","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"runId","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"200":{"description":"The NACHA file","content":{"text/plain":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Downloads the NACHA file for a run, for submission to your bank. Handle it as a payment instruction: it contains every employee's bank details and the amounts owed.\n\n#### Signature\n\n```http\nGET /business-made/payroll/runs/{runId}/ach/download (runId: string) -> The NACHA file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Contains full bank details for every employee in the run.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/runs/{runId}/ach/generate`"}},"/business-made/payroll/stubs/{stubId}/pdf/generate":{"post":{"operationId":"PayrollConfigController_generateStubPdf","summary":"Generate a pay stub PDF","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"stubId","required":true,"in":"path","schema":{"type":"string"},"description":"Pay stub id.","example":"STB-4821"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated PDF reference","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Generates the PDF for a pay stub, or returns the cached one. Pass `regenerate` to force a fresh render after correcting the underlying stub.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/stubs/{stubId}/pdf/generate (stubId: string, regenerate?: string) -> The generated PDF reference\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/stubs/{stubId}/pdf`"}},"/business-made/payroll/stubs/{stubId}/pdf":{"get":{"operationId":"PayrollConfigController_downloadStubPdf","summary":"Download a pay stub PDF","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"stubId","required":true,"in":"path","schema":{"type":"string"},"description":"Pay stub id.","example":"STB-4821"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"200":{"description":"The pay stub PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Downloads a pay stub as a PDF. Contains pay, deductions and year-to-date figures — restrict it to the employee it belongs to.\n\n#### Signature\n\n```http\nGET /business-made/payroll/stubs/{stubId}/pdf (stubId: string, regenerate?: string) -> The pay stub PDF\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/stubs/{stubId}/pdf/generate`"}},"/business-made/payroll/runs/{runId}/stubs/pdf/generate":{"post":{"operationId":"PayrollConfigController_generateStubPdfsForRun","summary":"Generate all pay stub PDFs for a run","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"runId","required":true,"in":"path","schema":{"type":"string"},"description":"Payroll run id.","example":"PR-4821"}],"responses":{"201":{"description":"The generation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Payroll config"],"description":"Renders every pay stub in a run in one operation — the distribution step after processing.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/runs/{runId}/stubs/pdf/generate (runId: string) -> The generation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/stubs/run/{payrollRunId}`"}},"/business-made/payroll/tax-forms/w2/{employeeId}/{taxYear}":{"post":{"operationId":"PayrollConfigController_generateW2","summary":"Generate a W-2","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee id.","example":"EMP-4821"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated W-2","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Generates an employee's W-2 for a tax year from their processed runs. Reconcile against the payroll summary first — a W-2 issued with wrong figures has to be corrected on a W-2c.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/tax-forms/w2/{employeeId}/{taxYear} (employeeId: string, taxYear: string, regenerate?: string) -> The generated W-2\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Correcting an issued W-2 means filing a W-2c — check the figures before generating.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/reports/w2/{employeeId}/{year}`\n- `POST /business-made/payroll/filings/w3/{taxYear}`"}},"/business-made/payroll/tax-forms/1099-nec/{contractorId}/{taxYear}":{"post":{"operationId":"PayrollConfigController_generate1099Nec","summary":"Generate a 1099-NEC","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"contractorId","required":true,"in":"path","schema":{"type":"string"},"description":"Contractor id.","example":"CON-4821"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated 1099-NEC","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Generates a 1099-NEC for a contractor for a tax year. Contractors are reported separately from employees — someone misclassified will appear on the wrong form.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/tax-forms/1099-nec/{contractorId}/{taxYear} (contractorId: string, taxYear: string, regenerate?: string) -> The generated 1099-NEC\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/filings/1096/{taxYear}`"}},"/business-made/payroll/tax-forms/year-end/{taxYear}":{"post":{"operationId":"PayrollConfigController_generateYearEnd","summary":"Generate all year-end forms","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Generates the full set of year-end forms for a tax year in one operation — every W-2 and 1099-NEC. Run the payroll summary and readiness checks first; this is the point errors become filed documents.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/tax-forms/year-end/{taxYear} (taxYear: string, regenerate?: string) -> The generation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Check `GET /business-made/payroll/employer-setup/readiness` before running.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-forms`"}},"/business-made/payroll/tax-forms":{"get":{"operationId":"PayrollConfigController_listTaxForms","summary":"List tax forms","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":false,"in":"query","schema":{"type":"string"},"example":"2026"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"w2"}],"responses":{"200":{"description":"Tax forms","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"The tax forms generated for the org, with their year and status.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-forms (taxYear?: string, type?: string) -> Tax forms\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-forms/{formId}/download`"}},"/business-made/payroll/tax-forms/{formId}/download":{"get":{"operationId":"PayrollConfigController_downloadTaxForm","summary":"Download a tax form","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"formId","required":true,"in":"path","schema":{"type":"string"},"description":"Form id.","example":"FRM-4821"}],"responses":{"200":{"description":"The form document","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Downloads a generated tax form. These contain full earnings and identifying details — restrict access accordingly.\n\n#### Signature\n\n```http\nGET /business-made/payroll/tax-forms/{formId}/download (formId: string) -> The form document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-forms`"}},"/business-made/payroll/filings/941/{taxYear}/{quarter}":{"post":{"operationId":"PayrollConfigController_generate941","summary":"Generate Form 941","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"quarter","required":true,"in":"path","schema":{"type":"string"},"description":"Calendar quarter, 1–4.","example":"3"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated 941","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Employer's Quarterly Federal Tax Return for a year and quarter — the return reconciling federal tax withheld against deposits made. Generated from processed runs, so unprocessed payroll is simply absent from it.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/filings/941/{taxYear}/{quarter} (taxYear: string, quarter: string, regenerate?: string) -> The generated 941\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only processed runs contribute. Confirm the quarter is closed before generating.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/filings/940/{taxYear}`"}},"/business-made/payroll/filings/940/{taxYear}":{"post":{"operationId":"PayrollConfigController_generate940","summary":"Generate Form 940","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated 940","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Employer's Annual FUTA Tax Return for a year — federal unemployment tax. Annual rather than quarterly.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/filings/940/{taxYear} (taxYear: string, regenerate?: string) -> The generated 940\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/filings/941/{taxYear}/{quarter}`"}},"/business-made/payroll/filings/w3/{taxYear}":{"post":{"operationId":"PayrollConfigController_generateW3","summary":"Generate Form W-3","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated W-3","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Transmittal of Wage and Tax Statements — **sums the year's W-2s**. Generate the W-2s first: the W-3 totals whatever exists, so a missing W-2 produces a transmittal that understates the year.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/filings/w3/{taxYear} (taxYear: string, regenerate?: string) -> The generated W-3\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Depends on the W-2s already existing. Generate year-end forms first.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/tax-forms/year-end/{taxYear}`"}},"/business-made/payroll/filings/efw2/{taxYear}":{"post":{"operationId":"PayrollConfigController_generateEfw2","summary":"Build the SSA EFW2 W-2 file","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"{ submission, fileName, recordCount, errors, totals } — the file text is never returned (it holds full SSNs)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Builds the SSA EFW2 wage file (Publication 42-007) from the year's W-2s — the file the owner validates in AccuWage Online and uploads to Business Services Online. Fixed-width 512-byte records: RA submitter, RE employer, RW per employee, RO when an optional amount exists, RS per state with wage data, RT/RU/RV totals, RF final. Recorded as an e-file submission (agency `ssa`, program `efw2`, env `file`): status `ready` with the stored file, or `error` with the list of problems and no file.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/filings/efw2/{taxYear} (taxYear: string, regenerate?: string) -> { submission, fileName, recordCount, errors, totals } — the file text is never returned (it holds full SSNs)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Needs the BSO User ID in the employer setup (`bsoUserId`), plus contact name, phone and e-mail — the generator returns `EFW2_BSO_USER_ID_MISSING` without it.\n- Returns the latest ready file for the year unless `regenerate=true`.\n- Generate the W-2s first; the file reports whatever W-2s exist.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/filings/efw2/{taxYear}/download`\n- `POST /business-made/payroll/filings/w3/{taxYear}`\n- `GET /business-made/efile/submissions`"}},"/business-made/payroll/filings/efw2/{taxYear}/download":{"get":{"operationId":"PayrollConfigController_downloadEfw2","summary":"Download the SSA EFW2 W-2 file","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"submissionId","required":false,"in":"query","schema":{"type":"string"},"example":"EF-3K9QZA"}],"responses":{"200":{"description":"The EFW2 file","content":{"text/plain":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Downloads the latest ready EFW2 file for the year (or the one named by `submissionId`) as plain text, CR/LF-delimited, ready for AccuWage and BSO upload. Contains full SSNs — restrict access accordingly.\n\n#### Signature\n\n```http\nGET /business-made/payroll/filings/efw2/{taxYear}/download (taxYear: string, submissionId?: string) -> The EFW2 file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/filings/efw2/{taxYear}`"}},"/business-made/payroll/filings/1096/{taxYear}":{"post":{"operationId":"PayrollConfigController_generate1096","summary":"Generate Form 1096","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated 1096","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Annual Summary and Transmittal — **sums the year's 1099-NECs**. Same dependency as the W-3: generate the 1099s first or the totals will be short.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/filings/1096/{taxYear} (taxYear: string, regenerate?: string) -> The generated 1096\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/payroll/tax-forms/1099-nec/{contractorId}/{taxYear}`"}},"/business-made/payroll/filings/state-quarterly/{taxYear}/{quarter}/{state}":{"post":{"operationId":"PayrollConfigController_generateStateQuarterly","summary":"Generate a state quarterly wage report","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2026"},{"name":"quarter","required":true,"in":"path","schema":{"type":"string"},"description":"Calendar quarter, 1–4.","example":"3"},{"name":"state","required":true,"in":"path","schema":{"type":"string"},"description":"State code.","example":"CA"},{"name":"regenerate","required":false,"in":"query","schema":{"type":"string"},"description":"Force a fresh generation instead of returning the cached document. Send `true`.","example":"true"}],"responses":{"201":{"description":"The generated report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"The state quarterly wage report for a year, quarter and state. One filing per state you have employees in — check the coverage report so no state is missed.\n\n#### Signature\n\n```http\nPOST /business-made/payroll/filings/state-quarterly/{taxYear}/{quarter}/{state} (taxYear: string, quarter: string, state: string, regenerate?: string) -> The generated report\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A state with employees but no filing is a missed obligation — reconcile against `tax-rules/coverage`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-rules/coverage`"}},"/business-made/payroll/filings/{formId}/download":{"get":{"operationId":"PayrollConfigController_downloadFiling","summary":"Download a filing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"formId","required":true,"in":"path","schema":{"type":"string"},"description":"Filing id.","example":"FIL-4821"}],"responses":{"200":{"description":"The filing document","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Filings"],"description":"Downloads a generated statutory filing, ready for submission.\n\n#### Signature\n\n```http\nGET /business-made/payroll/filings/{formId}/download (formId: string) -> The filing document\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/payroll/tax-forms`"}},"/workflow/fire":{"post":{"operationId":"WorkflowController_fire","summary":"Fire a record into a workflow","description":"The generic entry point: takes any record and starts a Task for it on a pipeline. This is how check-in puts a Reservation on `reservation-checkin-pipeline` and how POS puts an order on `prep-pipeline`.\n\nIdentify the pipeline with **either** `workflowId` (an existing definition's `sk`) **or** `workflowName`. The name form auto-bootstraps the definition from a registered template if the org does not have it yet, so a first call in a fresh org works without setup.\n\n#### Signature\n\n```http\nPOST /workflow/fire (body) -> The created Task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/definition/from-template/{name}`\n- `GET /workflow/task`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The record to fire and the pipeline to put it on.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["datatype","id"],"properties":{"datatype":{"type":"string","description":"DataType of the owning record.","example":"reservation"},"id":{"type":"string","description":"Id of the owning record.","example":"RES-771"},"workflowId":{"type":"string","description":"Existing definition `sk`.","example":"WF-12"},"workflowName":{"type":"string","description":"Definition name; bootstrapped from template when missing.","example":"reservation-checkin-pipeline"},"data":{"type":"object","description":"Extra fields to seed on the Task.","additionalProperties":true}}},"examples":{"byName":{"summary":"By name — bootstraps the pipeline if needed","value":{"datatype":"reservation","id":"RES-771","workflowName":"reservation-checkin-pipeline"}},"byId":{"summary":"Onto a specific definition","value":{"datatype":"order","id":"ORD-4821","workflowId":"WF-12","data":{"assignTo":["kitchen@example.com"]}}}}}}},"responses":{"201":{"description":"The created Task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"]}},"/workflow/for/{datatype}":{"get":{"operationId":"WorkflowController_explain","summary":"Which workflow takes a datatype, and why","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"Collection / datatype name.","example":"access_request"}],"responses":{"200":{"description":"{ datatype, collection, effective, chosen, listing, active, reason }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"datatype":"access_request","collection":{"sk":"COL-9","workflow":null,"enableWorkflow":true},"effective":{"sk":"WF-12","name":"access-approval","title":"Access approval","enabled":true},"chosen":null,"listing":[{"sk":"WF-12","name":"access-approval","title":"Access approval","enabled":true}],"active":true,"reason":"runs \"access-approval\": it lists this collection"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Explains what happens when a record of this datatype fires a workflow: the collection's own `workflow` choice and its Enable Workflow switch, every definition that lists the datatype in `collections`, the one that wins (`effective`), whether it will run (`active`), and a one-line `reason` — e.g. `off: the collection has Enable Workflow switched off`, `none: no workflow names this collection`, `runs \"access-approval\": it lists this collection`.\n\n#### Signature\n\n```http\nGET /workflow/for/{datatype} (datatype: string) -> { datatype, collection, effective, chosen, listing, active, reason }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/fire`"}},"/workflow/stations":{"get":{"operationId":"WorkflowController_stations","summary":"Get the kitchen board scope","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ names, pipeline, stations }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"names":["prep-pipeline"],"pipeline":{"id":"WF-3","name":"prep-pipeline","title":"Kitchen prep"},"stations":["Grill","Fryer","Bar"]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"What a prep/kitchen board shows: the `prep-pipeline` workflow (when the org has one) and the station names from the `prepStation` product attribute's options, de-duplicated.\n\n#### Signature\n\n```http\nGET /workflow/stations () -> { names, pipeline, stations }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workflow/definition":{"get":{"operationId":"WorkflowController_listDefinitions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":false,"in":"query","schema":{"type":"string"},"description":"Exact definition name.","example":"prep-pipeline"}],"responses":{"200":{"description":"Definitions (first 200)","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"summary":"List workflow definitions","description":"The pipelines defined for the org. Optionally filter by name.\n\n#### Signature\n\n```http\nGET /workflow/definition (name?: string) -> Definitions (first 200)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Capped at 200 definitions; there is no paging on this route.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/definition/{id}`"},"post":{"operationId":"WorkflowController_createDefinition","summary":"Create a workflow definition","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created definition","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Creates a pipeline from a payload — stages in order, plus optional SLA escalation tiers. Use the template route instead for the standard pipelines.\n\n#### Signature\n\n```http\nPOST /workflow/definition (body) -> The created definition\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/definition/from-template/{name}`","requestBody":{"description":"The definition.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","example":"onboarding-pipeline"},"title":{"type":"string","example":"Customer onboarding"},"stages":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Ordered stages; a Task's `stageId` indexes into this."},"escalations":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"name":"onboarding-pipeline","title":"Customer onboarding","stages":[{"name":"Received"},{"name":"In review"},{"name":"Approved"}]}}}}}},"/workflow/definition/{id}":{"get":{"operationId":"WorkflowController_getDefinition","summary":"Get a workflow definition","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Definition `sk`.","example":"WF-12"}],"responses":{"200":{"description":"The definition","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Fetches one definition with its stages and escalation tiers.\n\n#### Signature\n\n```http\nGET /workflow/definition/{id} (id: string) -> The definition\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /workflow/definition/{id}`"},"patch":{"operationId":"WorkflowController_updateDefinition","summary":"Update a workflow definition","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Definition `sk`.","example":"WF-12"}],"responses":{"200":{"description":"The update result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Updates title, description, stages or escalations. Every top-level key in the body is written to `data.<key>`, so sending `stages` replaces the whole array rather than merging into it.\n\n**Tasks already in flight keep their numeric `stageId`.** Reordering or removing stages therefore silently re-points running tasks at whatever now sits at that index. Add stages at the end, or drain the pipeline first.\n\n#### Signature\n\n```http\nPATCH /workflow/definition/{id} (id: string, body) -> The update result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `stageId` on in-flight tasks is an index — reordering stages moves those tasks.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/task`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Kitchen prep","escalations":[{"afterMinutes":15,"notify":["manager@example.com"]}]}}}}},"delete":{"operationId":"WorkflowController_deleteDefinition","summary":"Delete a workflow definition","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Definition `sk`.","example":"WF-12"}],"responses":{"200":{"description":"The delete result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Deletes a pipeline definition. Tasks already created against it are **not** deleted and keep their `workflowId`, leaving them pointing at a definition that no longer exists — stage names and SLA no longer resolve. Drain or cancel the tasks first.\n\n#### Signature\n\n```http\nDELETE /workflow/definition/{id} (id: string) -> The delete result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Orphans any in-flight tasks on this definition.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/cancel`"}},"/workflow/definition/from-template/{name}":{"post":{"operationId":"WorkflowController_createFromTemplate","summary":"Create a workflow from a template","description":"Creates one of the registered pipelines for this org if it does not already exist: `prep-pipeline`, `reservation-checkin-pipeline`, `pickup-pipeline`, `application-processing-pipeline`, `service-appointment-pipeline`, `renewal-pipeline`.\n\nIdempotent — if the org already has the definition, the existing one is returned untouched, so it is safe to call on every boot or before firing.\n\n#### Signature\n\n```http\nPOST /workflow/definition/from-template/{name} (name: string) -> The new or existing definition\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Idempotent — will not overwrite an existing definition of the same name.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/fire`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Template name.","example":"prep-pipeline"}],"responses":{"201":{"description":"The new or existing definition","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"]}},"/workflow/task":{"get":{"operationId":"WorkflowController_listTasks","summary":"List tasks","description":"The live queue. By default returns **active** tasks (`new`, `pending`, `inprogress`, `blocked`) plus tasks completed in the last 24 hours, excluding archived ones — which is what an operations board wants to show.\n\nTwo ways to widen it: `recentDoneHours` changes the completed window (`0` drops completed tasks entirely, `-1` returns every completed task ever), and an explicit `status` overrides the whole default and is used as-is.\n\n#### Signature\n\n```http\nGET /workflow/task (workflowId?: string, ownerDatatype?: string, ownerId?: string, stageId?: integer, status?: string, assignTo?: string, pageSize?: integer, recentDoneHours?: number, includeArchived?: boolean) -> Matching tasks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Always page 1 — use `pageSize` to widen, there is no page parameter.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/task/{taskId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"workflowId","required":false,"in":"query","schema":{"type":"string"},"example":"WF-12"},{"name":"ownerDatatype","required":false,"in":"query","schema":{"type":"string"},"description":"Owning record type.","example":"reservation"},{"name":"ownerId","required":false,"in":"query","schema":{"type":"string"},"example":"RES-771"},{"name":"stageId","required":false,"in":"query","schema":{"type":"integer"},"description":"Exact stage index.","example":2},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated. Overrides the active/recent-done default entirely.","example":"inprogress,blocked"},{"name":"assignTo","required":false,"in":"query","schema":{"type":"string"},"example":"ops@example.com"},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"description":"Default 200.","example":200},{"name":"recentDoneHours","required":false,"in":"query","schema":{"type":"number"},"description":"Rolling window for completed tasks. Default 24. `0` excludes all completed; `-1` returns all completed forever.","example":24},{"name":"includeArchived","required":false,"in":"query","schema":{"type":"boolean"},"description":"Default false.","example":false},{"name":"station","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Matching tasks","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"]}},"/workflow/task/{taskId}/archive":{"post":{"operationId":"WorkflowController_archiveTask","summary":"Archive a task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"201":{"description":"The archived task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Hides a task from the default list without changing its status — the task is not completed or canceled, just out of the way. `GET /workflow/task?includeArchived=true` still returns it.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/archive (taskId: string) -> The archived task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A display flag, not a status change.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/unarchive`"}},"/workflow/task/{taskId}/unarchive":{"post":{"operationId":"WorkflowController_unarchiveTask","summary":"Unarchive a task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"201":{"description":"The unarchived task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Returns an archived task to the default list.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/unarchive (taskId: string) -> The unarchived task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/archive`"}},"/workflow/task/{taskId}":{"get":{"operationId":"WorkflowController_getTask","summary":"Get a task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"200":{"description":"The task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Fetches one task with its stage, status, assignment and comments.\n\n#### Signature\n\n```http\nGET /workflow/task/{taskId} (taskId: string) -> The task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/task/{taskId}/history`"}},"/workflow/task/{taskId}/history":{"get":{"operationId":"WorkflowController_getHistory","summary":"Get a task's stage history","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"200":{"description":"Stage transitions, oldest first","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}},"example":[{"stageId":0,"at":"2026-08-30T09:00:00.000Z"},{"stageId":1,"at":"2026-08-30T09:04:12.000Z"}]}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"The stage-transition history — when the task entered each stage. Operator notes are deliberately kept out of this list so time-in-stage analytics stay clean. Returns an empty array for an unknown task id.\n\n#### Signature\n\n```http\nGET /workflow/task/{taskId}/history (taskId: string) -> Stage transitions, oldest first\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/note`"}},"/workflow/task/{taskId}/advance":{"post":{"operationId":"WorkflowController_advance","summary":"Advance a task one stage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"201":{"description":"The advanced task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/workflow/task/{taskId}/advance","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/workflow/task/{taskId}/advance","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't work the <station> station: <requirement titles>. — A requirement rule whose `enforcement.kitchenStation` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"kitchenStation","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Moves the task to the next stage (`stageId + 1`) and fires the stage-change hooks — alerts, assignments and SLA timers for the new stage.\n\nThe increment is **not bounded by the definition's stage count**: repeated calls will push `stageId` past the last stage, where it resolves to no stage at all. Use `complete` to finish a task.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/advance (taskId: string, body) -> The advanced task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns HTTP 200 with a `null` body when the task id does not exist — check for `null` rather than relying on a 404.\n- Not bounded by the stage count — use `complete` to terminate.\n- Kitchen station gate: a prep ticket (`data.payload.station`) can only be moved by someone cleared for that station — 423 `readiness_block` (`taskId`, `station`); resend with `override`. Tickets without a station are never gated.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `423` | READINESS_BLOCK | <name> can't work the <station> station: <requirement titles>. | A requirement rule whose `enforcement.kitchenStation` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/complete`\n- `POST /workflow/task/{taskId}/move-to/{stageId}`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}}}},"/workflow/task/{taskId}/move-to/{stageId}":{"post":{"operationId":"WorkflowController_moveTo","summary":"Move a task to a specific stage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"},{"name":"stageId","required":true,"in":"path","schema":{"type":"string"},"description":"Target stage index (zero-based).","example":3}],"responses":{"201":{"description":"The moved task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/workflow/task/{taskId}/move-to/{stageId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/workflow/task/{taskId}/move-to/{stageId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't work the <station> station: <requirement titles>. — A requirement rule whose `enforcement.kitchenStation` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"kitchenStation","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Jumps a task to any stage, forwards or backwards, firing the stage-change hooks. Backwards moves are allowed — that is how a task is sent back for rework — but the history keeps both transitions, so time-in-stage figures count the stage twice.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/move-to/{stageId} (taskId: string, stageId: string, body) -> The moved task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns HTTP 200 with a `null` body when the task id does not exist — check for `null` rather than relying on a 404.\n- The stage index is not validated against the definition.\n- Kitchen station gate — same as advance.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `423` | READINESS_BLOCK | <name> can't work the <station> station: <requirement titles>. | A requirement rule whose `enforcement.kitchenStation` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/advance`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}}}},"/workflow/task/{taskId}/complete":{"post":{"operationId":"WorkflowController_complete","summary":"Complete a task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"201":{"description":"The completed task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"400":{"description":"An override needs reasonCode, reason, expiresAt. — `override` is missing a field, has an unknown reason code, or an expiry in the past.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An override needs reasonCode, reason, expiresAt.","path":"/workflow/task/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Sign in as a location manager or HR to override.","path":"/workflow/task/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"423":{"description":"<name> can't work the <station> station: <requirement titles>. — A requirement rule whose `enforcement.kitchenStation` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"kitchenStation","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Terminates the task — advances it to the terminal stage and marks it done. This is the correct way to finish a task; it stamps the completion time the recent-done window in `GET /workflow/task` filters on.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/complete (taskId: string, body) -> The completed task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Kitchen station gate — same as advance.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `423` | READINESS_BLOCK | <name> can't work the <station> station: <requirement titles>. | A requirement rule whose `enforcement.kitchenStation` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n| `400` | OVERRIDE_INVALID | An override needs reasonCode, reason, expiresAt. | `override` is missing a field, has an unknown reason code, or an expiry in the past. | — |\n| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/cancel`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"override":{"type":"object","description":"Readiness override (manager/HR): lets this action go ahead despite a block. Recorded per blocking requirement.","properties":{"reasonCode":{"type":"string","enum":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"]},"reason":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}}}},"/workflow/task/{taskId}/cancel":{"post":{"operationId":"WorkflowController_cancelTask","summary":"Cancel a task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"201":{"description":"The canceled task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Sets the task status to `canceled`. Distinct from completing it — a canceled task did not finish its pipeline, and it drops out of the default task list without appearing in the recent-done window.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/cancel (taskId: string) -> The canceled task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns HTTP 200 with a `null` body when the task id does not exist — check for `null` rather than relying on a 404.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/complete`"}},"/workflow/task/restart":{"post":{"operationId":"WorkflowController_restartOwnerTasks","summary":"Restart an owner's tasks","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The owning record.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["datatype","id"],"properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"example":{"datatype":"reservation","id":"RES-771"}}}},"responses":{"201":{"description":"The restarted tasks","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Sends **every** task belonging to one owning record back to stage 0. Used when a record has to go through its pipeline again — a reservation re-checked-in, an order re-fired to the kitchen.\n\nThis re-runs the stage-0 hooks, so any alerts or notifications wired to the first stage fire again.\n\n#### Signature\n\n```http\nPOST /workflow/task/restart (body) -> The restarted tasks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Affects all tasks for the owner, not just the active one.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/fire`"}},"/workflow/task/{taskId}/reassign":{"post":{"operationId":"WorkflowController_reassign","summary":"Reassign a task","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"requestBody":{"description":"The new assignee list.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignTo"],"properties":{"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com","lead@example.com"]}}},"example":{"assignTo":["ops@example.com","lead@example.com"]}}}},"responses":{"201":{"description":"The reassigned task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Replaces the task's assignee list. The body **replaces** `assignTo` outright — send the full list, including anyone who should stay assigned.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/reassign (taskId: string, body) -> The reassigned task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Replaces rather than appends.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/task`"}},"/workflow/task/{taskId}/note":{"post":{"operationId":"WorkflowController_addNote","summary":"Add a note to a task","description":"Appends an operator note to the task's `comments`, stamped with the caller's email and the stage the task was on at the time. Notes are kept out of the stage history on purpose, so adding them never distorts time-in-stage analytics.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/note (taskId: string, body) -> The task with the note appended\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns HTTP 200 with a `null` body when the task id does not exist — check for `null` rather than relying on a 404.\n- The author is taken from the token, not the body.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/task/{taskId}/history`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"requestBody":{"description":"The note.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string","example":"Customer called, running 20 minutes late"}}},"example":{"note":"Customer called, running 20 minutes late"}}}},"responses":{"201":{"description":"The task with the note appended","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"]}},"/workflow/analytics/{workflowId}/wait-times":{"get":{"operationId":"WorkflowController_waitStats","summary":"Get wait-time statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"workflowId","required":true,"in":"path","schema":{"type":"string"},"description":"Definition `sk`.","example":"WF-12"},{"name":"windowDays","required":false,"in":"query","schema":{"type":"integer"},"description":"Rolling window in days. Default 7.","example":7},{"name":"refresh","required":true,"in":"query","schema":{"type":"string"}},{"name":"station","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Per-stage and total timings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Per-stage timing for one pipeline over a rolling window — average, median and p95 time in each stage, plus total flow time. p95 is the number to watch: an average that looks fine often hides a stage where one task in twenty stalls.\n\n#### Signature\n\n```http\nGET /workflow/analytics/{workflowId}/wait-times (workflowId: string, windowDays?: integer) -> Per-stage and total timings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/analytics/task/{taskId}/eta`"}},"/workflow/analytics/task/{taskId}/eta":{"get":{"operationId":"WorkflowController_taskEta","summary":"Estimate a task's remaining time","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"200":{"description":"The estimate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Projects how long an in-flight task has left, using the historical stage timings for its pipeline. An estimate from past throughput — not a commitment, and it is only as good as the history behind it.\n\n#### Signature\n\n```http\nGET /workflow/analytics/task/{taskId}/eta (taskId: string) -> The estimate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/analytics/{workflowId}/wait-times`"}},"/workflow/escalation/scan":{"post":{"operationId":"WorkflowController_scanNow","summary":"Run the overdue scan now","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ orgId, escalated, lastRun } — `escalated` counts tasks moved by this scan; `lastRun` is the last scheduled sweep across all orgs, or null","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"orgId":"acme","escalated":2,"lastRun":{"at":"2026-09-29T13:50:00.000Z","orgs":41,"escalated":6}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Runs this org's SLA escalation scan immediately instead of waiting for the scheduled one (every 10 minutes): open tasks past their `dueDate` move to their next escalation tier and their notifications go out.\n\n#### Signature\n\n```http\nPOST /workflow/escalation/scan () -> { orgId, escalated, lastRun } — `escalated` counts tasks moved by this scan; `lastRun` is the last scheduled sweep across all orgs, or null\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/escalation/breached`"}},"/workflow/escalation/breached":{"get":{"operationId":"WorkflowController_breached","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"workflowId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one pipeline.","example":"WF-12"}],"responses":{"200":{"description":"Breached tasks","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"summary":"List breached tasks","description":"Tasks past their `dueDate` and still open — what is already late right now.\n\n#### Signature\n\n```http\nGET /workflow/escalation/breached (workflowId?: string) -> Breached tasks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/escalation/upcoming`"}},"/workflow/escalation/upcoming":{"get":{"operationId":"WorkflowController_upcoming","summary":"List tasks about to breach","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"withinMinutes","required":false,"in":"query","schema":{"type":"integer"},"description":"Look-ahead window. Default 30.","example":30},{"name":"workflowId","required":false,"in":"query","schema":{"type":"string"},"example":"WF-12"}],"responses":{"200":{"description":"Tasks approaching breach","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Tasks whose `dueDate` falls within the next N minutes — the window in which intervening still helps.\n\n#### Signature\n\n```http\nGET /workflow/escalation/upcoming (withinMinutes?: integer, workflowId?: string) -> Tasks approaching breach\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/escalation/breached`"}},"/workflow/task/{taskId}/sla":{"get":{"operationId":"WorkflowController_slaForTask","summary":"Get a task's SLA snapshot","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"200":{"description":"The SLA snapshot","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"The SLA position for one task — current escalation tier, `dueDate`, time remaining and escalation history.\n\n#### Signature\n\n```http\nGET /workflow/task/{taskId}/sla (taskId: string) -> The SLA snapshot\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /workflow/task/{taskId}/snooze`"}},"/workflow/task/{taskId}/snooze":{"post":{"operationId":"WorkflowController_snooze","summary":"Snooze a task's SLA","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"requestBody":{"description":"How long to snooze for.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["minutes"],"properties":{"minutes":{"type":"number","example":15}}},"example":{"minutes":15}}}},"responses":{"201":{"description":"The task with its new dueDate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"owner":{"type":"object","description":"The record this task represents.","properties":{"datatype":{"type":"string","example":"reservation"},"id":{"type":"string","example":"RES-771"}}},"data":{"type":"object","additionalProperties":true,"properties":{"workflowId":{"type":"string","example":"WF-12"},"stageId":{"type":"integer","description":"Zero-based index into the definition's stages.","example":2},"status":{"type":"string","enum":["new","pending","inprogress","blocked","done","canceled"],"example":"inprogress"},"assignTo":{"type":"array","items":{"type":"string"},"example":["ops@example.com"]},"dueDate":{"type":"string","format":"date-time","example":"2026-09-01T14:00:00.000Z"},"archived":{"type":"boolean","example":false},"history":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Stage transitions only — notes are kept out of it."}}},"comments":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Operator notes."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Pushes `dueDate` forward by N minutes, so the task stops counting as breached. Measured from the existing `dueDate` when there is one, otherwise from now — snoozing an already-overdue task by 10 minutes moves it 10 minutes past its *original* deadline, which may leave it still breached.\n\nA negative value pulls the deadline in.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/snooze (taskId: string, body) -> The task with its new dueDate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns HTTP 200 with a `null` body when the task id does not exist — check for `null` rather than relying on a 404.\n- Relative to the existing dueDate, not to now.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/task/{taskId}/sla`"}},"/workflow/task/{taskId}/escalate-now":{"post":{"operationId":"WorkflowController_escalateNow","summary":"Escalate a task immediately","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (`sk`).","example":"TASK-4821"}],"responses":{"201":{"description":"The escalated task","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workflow"],"description":"Marks the task overdue and moves it to the next escalation tier without waiting for the SLA timer — the manual pull for something that needs attention now. Fires that tier's notifications.\n\n#### Signature\n\n```http\nPOST /workflow/task/{taskId}/escalate-now (taskId: string) -> The escalated task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends the tier's notifications immediately.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workflow/escalation/breached`"}},"/approval/ask":{"post":{"operationId":"ApprovalController_ask","summary":"Ask for access, or for a guarded operation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The decision state","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"pending":{"summary":"Sent","value":{"decision":"pending","requestId":"66f1c0ffee12ab34cd56ef70","taskId":"66f1c0ffee12ab34cd56ef78","approvers":["owner@acme.com"],"escalatesAt":"2026-09-30T14:00:00.000Z"}},"alreadyPending":{"summary":"Already asked","value":{"decision":"already-pending","requestId":"66f1c0ffee12ab34cd56ef70","taskId":"66f1c0ffee12ab34cd56ef78","approvers":["owner@acme.com"],"since":"2026-09-29T10:00:00.000Z"}},"allowed":{"summary":"Record operation, no approval needed","value":{"decision":"allowed"}}}}}},"400":{"description":"target.path is required — A screen or menu ask without a path.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"target.path is required","path":"/approval/ask","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in to ask — The caller has no email on their session.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to ask","path":"/approval/ask","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"page 66f1c0ffee12ab34cd56ef78 not found — The record to publish does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"page 66f1c0ffee12ab34cd56ef78 not found","path":"/approval/ask","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"Access requests are switched off in this org: no enabled workflow takes access_request (check the collection's Enable Workflow and the workflow's Enabled switch) — A screen/menu ask when the access workflow is disabled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Access requests are switched off in this org: no enabled workflow takes access_request (check the collection's Enable Workflow and the workflow's Enabled switch)","path":"/approval/ask","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"Two kinds of ask:\n\n- **A screen or menu item** — `target: { kind: \"screen\" | \"menu\", path, app? }`. Opens an `access_request` and fires the access workflow; the approvers get it in their tray and mail, and the asker is told it was sent. The same open ask for the same path returns `already-pending` instead of a second request.\n- **An operation on a record** — `target: { kind: \"record\", operation: \"publish\", datatype, id }`, for publishing a page, post or product. When no enabled workflow takes that datatype there is nothing to wait for and the answer is `{ decision: \"allowed\" }` — the caller simply goes ahead.\n\n`request.reason` is shown to the approvers; `request.label` names the screen in messages (defaults to `path`).\n\n#### Signature\n\n```http\nPOST /approval/ask (body) -> The decision state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_TO_ASK | Sign in to ask | The caller has no email on their session. | — |\n| `400` | PATH_REQUIRED | target.path is required | A screen or menu ask without a path. | — |\n| `404` | RECORD_NOT_FOUND | page 66f1c0ffee12ab34cd56ef78 not found | The record to publish does not exist. | — |\n| `422` | ACCESS_REQUESTS_OFF | Access requests are switched off in this org: no enabled workflow takes access_request (check the collection's Enable Workflow and the workflow's Enabled switch) | A screen/menu ask when the access workflow is disabled. | — |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /approval/tray`\n- `POST /approval/request/{id}/cancel`","requestBody":{"description":"What is asked for.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["target"],"properties":{"target":{"type":"object","properties":{"kind":{"type":"string","enum":["menu","screen","record"]},"path":{"type":"string"},"app":{"type":"string","enum":["appmint","business-made"],"default":"appmint"},"operation":{"type":"string","enum":["publish"]},"datatype":{"type":"string"},"id":{"type":"string"}}},"request":{"type":"object","properties":{"reason":{"type":"string"},"label":{"type":"string"}}}}},"examples":{"screen":{"summary":"Access to a screen","value":{"target":{"kind":"screen","path":"/crm/leads","app":"appmint"},"request":{"reason":"I cover the front desk on Fridays","label":"Leads"}}},"publish":{"summary":"Publish a page","value":{"target":{"kind":"record","operation":"publish","datatype":"page","id":"66f1c0ffee12ab34cd56ef78"},"request":{"reason":"Fall menu is ready"}}}}}}}}},"/approval/tray":{"get":{"operationId":"ApprovalController_tray","summary":"Get my approval tray","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ waitingOnMe, myRequests, notices, unread, counts }","content":{"application/json":{"schema":{"type":"object","properties":{"waitingOnMe":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Task `sk`."},"title":{"type":"string","example":"Access to /crm/leads"},"summary":{"type":"string"},"status":{"type":"string","example":"pending"},"stageId":{"type":"integer"},"workflowName":{"type":"string","example":"access-approval"},"assignTo":{"type":"array","items":{"type":"string"}},"dueDate":{"type":"string"},"createdate":{"type":"string"},"owner":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"}}},"payload":{"type":"object","additionalProperties":true},"history":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"myRequests":{"type":"array","items":{"type":"object","additionalProperties":true}},"notices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"text":{"type":"string"},"read":{"type":"boolean"},"createdate":{"type":"string"},"kind":{"type":"string","enum":["task","mine","decision","access","system"],"description":"What the line is about, so the screen can take the person there."},"taskId":{"type":"string"}}}},"unread":{"type":"integer"},"counts":{"type":"object","properties":{"waiting":{"type":"integer"},"mine":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"Everything that needs the caller: decisions waiting on them (`waitingOnMe` — only tasks whose workflow is a decision, not work tasks or workspace cards), their own open access requests (`myRequests`), and their notices, newest first, with the unread count. Reads up to 200 waiting tasks, 100 requests and 300 notices.\n\n#### Signature\n\n```http\nGET /approval/tray () -> { waitingOnMe, myRequests, notices, unread, counts }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /approval/history`\n- `POST /approval/notices/read`"}},"/approval/history":{"get":{"operationId":"ApprovalController_history","summary":"Get my decided approvals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ decided, myRequests }","content":{"application/json":{"schema":{"type":"object","properties":{"decided":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Task `sk`."},"title":{"type":"string","example":"Access to /crm/leads"},"summary":{"type":"string"},"status":{"type":"string","example":"pending"},"stageId":{"type":"integer"},"workflowName":{"type":"string","example":"access-approval"},"assignTo":{"type":"array","items":{"type":"string"}},"dueDate":{"type":"string"},"createdate":{"type":"string"},"owner":{"type":"object","properties":{"datatype":{"type":"string"},"id":{"type":"string"}}},"payload":{"type":"object","additionalProperties":true},"history":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"myRequests":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"What the caller decided (`decided` — tasks they moved that are now approved, rejected, done or cancelled) and the answers to their own access requests (`myRequests`), newest first, up to 100 of each.\n\n#### Signature\n\n```http\nGET /approval/history () -> { decided, myRequests }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /approval/tray`"}},"/approval/task/{taskId}/approve":{"post":{"operationId":"ApprovalController_approve","summary":"Approve","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"The decision task `sk` (from the tray).","example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"{ taskId, decision, granted?, task }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"taskId":"66f1c0ffee12ab34cd56ef78","decision":"approve","granted":"group front-desk","task":{"id":"66f1c0ffee12ab34cd56ef78","status":"approved"}}}}},"400":{"description":"Say how: a role or a group to grant — Approving an access request without `grant.via` and `grant.name`. The body also carries `reason: \"grant-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say how: a role or a group to grant","path":"/approval/task/{taskId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This is not waiting on you — The caller is not one of the task's assignees and not an Owner, ConfigAdmin or RootAdmin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This is not waiting on you","path":"/approval/task/{taskId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Task not found — No task with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Task not found","path":"/approval/task/{taskId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already approved — The task is no longer open (the sentence names its status).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already approved","path":"/approval/task/{taskId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"This is a prep-pipeline task, not an approval — move it on its own board — The task's workflow has no Approved/Published and Rejected stages. The body also carries `reason: \"not-a-decision\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"This is a prep-pipeline task, not an approval — move it on its own board","path":"/approval/task/{taskId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"Moves the task to its workflow's \"yes\" stage (Approved, or Published for a publishing flow) and tells the person who asked.\n\n- **Access request**: `grant` is required and says how — `{ via: \"role\" | \"group\", name }`. The requester gets that role or group; access is never granted one-off.\n- **Publish request**: the page or post is published (a product is marked published).\n\nOnly an assignee of the task — or an Owner, ConfigAdmin or RootAdmin — may decide. Checks run before anything is granted, so a task that cannot be decided never half-happens.\n\n#### Signature\n\n```http\nPOST /approval/task/{taskId}/approve (taskId: string, body) -> { taskId, decision, granted?, task }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Task not found | No task with that id. | — |\n| `409` | ALREADY_DECIDED | Already approved | The task is no longer open (the sentence names its status). | — |\n| `403` | NOT_WAITING_ON_YOU | This is not waiting on you | The caller is not one of the task's assignees and not an Owner, ConfigAdmin or RootAdmin. | — |\n| `422` | NOT_A_DECISION | This is a prep-pipeline task, not an approval — move it on its own board | The task's workflow has no Approved/Published and Rejected stages. The body also carries `reason: \"not-a-decision\"`. | — |\n| `400` | GRANT_REQUIRED | Say how: a role or a group to grant | Approving an access request without `grant.via` and `grant.name`. The body also carries `reason: \"grant-required\"`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /approval/task/{taskId}/reject`","requestBody":{"description":"The note, and for access requests the grant.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"},"grant":{"type":"object","properties":{"via":{"type":"string","enum":["role","group"]},"name":{"type":"string"}}}}},"example":{"grant":{"via":"group","name":"front-desk"},"note":"Fridays only for now"}}}}}},"/approval/task/{taskId}/reject":{"post":{"operationId":"ApprovalController_reject","summary":"Reject","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"The decision task `sk` (from the tray).","example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"{ taskId, decision, task }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This is not waiting on you — The caller is not one of the task's assignees and not an Owner, ConfigAdmin or RootAdmin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This is not waiting on you","path":"/approval/task/{taskId}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Task not found — No task with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Task not found","path":"/approval/task/{taskId}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already approved — The task is no longer open (the sentence names its status).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already approved","path":"/approval/task/{taskId}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"This is a prep-pipeline task, not an approval — move it on its own board — The task's workflow has no Approved/Published and Rejected stages. The body also carries `reason: \"not-a-decision\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"This is a prep-pipeline task, not an approval — move it on its own board","path":"/approval/task/{taskId}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"Moves the task to its workflow's Rejected stage, records the note, and tells the person who asked. Same permission rule as approving.\n\n#### Signature\n\n```http\nPOST /approval/task/{taskId}/reject (taskId: string, body) -> { taskId, decision, task }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Task not found | No task with that id. | — |\n| `409` | ALREADY_DECIDED | Already approved | The task is no longer open (the sentence names its status). | — |\n| `403` | NOT_WAITING_ON_YOU | This is not waiting on you | The caller is not one of the task's assignees and not an Owner, ConfigAdmin or RootAdmin. | — |\n| `422` | NOT_A_DECISION | This is a prep-pipeline task, not an approval — move it on its own board | The task's workflow has no Approved/Published and Rejected stages. The body also carries `reason: \"not-a-decision\"`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /approval/task/{taskId}/approve`","requestBody":{"description":"Why.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"}}},"example":{"note":"Ask your manager first"}}}}}},"/approval/request/{id}/cancel":{"post":{"operationId":"ApprovalController_cancel","summary":"Withdraw my access request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"The `access_request` `sk` (`requestId` from the ask).","example":"66f1c0ffee12ab34cd56ef70"}],"responses":{"201":{"description":"{ requestId, status: \"cancelled\" }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Not your request — Someone else asked.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your request","path":"/approval/request/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Request not found — No request with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Request not found","path":"/approval/request/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already approved — The request is no longer pending (the sentence names its status).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already approved","path":"/approval/request/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"The person who asked takes it back while it is still pending. The workflow task is cancelled and the approvers are told.\n\n#### Signature\n\n```http\nPOST /approval/request/{id}/cancel (id: string, body) -> { requestId, status: \"cancelled\" }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Request not found | No request with that id. | — |\n| `403` | NOT_YOUR_REQUEST | Not your request | Someone else asked. | — |\n| `409` | ALREADY_DECIDED | Already approved | The request is no longer pending (the sentence names its status). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"description":"Why.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"No longer needed"}}}}}},"/approval/notices/read":{"post":{"operationId":"ApprovalController_readAll","summary":"Mark all my notices read","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ read }","content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"integer"}}},"example":{"read":4}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"Marks every unread tray notice addressed to the caller as read (up to 200 at a time).\n\n#### Signature\n\n```http\nPOST /approval/notices/read () -> { read }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /approval/notices/{id}/read`"}},"/approval/notices/{id}/read":{"post":{"operationId":"ApprovalController_readOne","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Notice id from the tray.","example":"66f1c0ffee12ab34cd56ef99"}],"responses":{"201":{"description":"{ read: 1 }","content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Not your notice — The notice does not exist or is not addressed to the caller.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your notice","path":"/approval/notices/{id}/read","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"summary":"Mark one notice read","description":"Marks one tray notice read. It must be addressed to the caller.\n\n#### Signature\n\n```http\nPOST /approval/notices/{id}/read (id: string) -> { read: 1 }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | NOT_YOUR_NOTICE | Not your notice | The notice does not exist or is not addressed to the caller. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/approval/setup":{"post":{"operationId":"ApprovalController_setup","summary":"Set up access requests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ workflow, seeded }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"workflow":"access-approval","seeded":false}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Approval"],"description":"Makes sure the org has an access workflow: when a workflow already lists `access_request` it is left alone (`seeded: false`); otherwise the shipped `access-approval` is created. Safe to repeat — asking runs it too.\n\n#### Signature\n\n```http\nPOST /approval/setup () -> { workflow, seeded }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/home":{"get":{"operationId":"WorkspaceController_home","summary":"Home","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"{ me, workspaces, conversations, unreadTotal, myTasks, upcoming, recent }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Everything the caller has joined, in one call: `workspaces` and `conversations` (each with `unread`), `unreadTotal`, `myTasks` (open tasks assigned to the caller, soonest due first, up to 20), `upcoming` (meetings from now, up to 10) and `recent` (the latest top-level items, expired ones left out). `me` is the caller as the workspace sees them (email, name, external).\n\n#### Signature\n\n```http\nGET /workspace/home () -> { me, workspaces, conversations, unreadTotal, myTasks, upcoming, recent }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /workspace/home/inbox`\n- `GET /workspace/home/tasks`"}},"/workspace/home/inbox":{"get":{"operationId":"WorkspaceController_inbox","summary":"Inbox and mentions","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"kind","required":false,"in":"query","schema":{"type":"string","enum":["mentions","direct","replies"]},"description":"Only one kind. Omit for all three."}],"responses":{"200":{"description":"{ data: Item[] }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Items across joined workspaces that are for the caller: `mention` (their email @mentioned), `direct` (messages from others in a direct conversation) and `reply` (replies to their own items). Newest first, up to 100. Each carries `kind`, `workspaceTitle`, `direct` and `unread` (newer than the caller's last read of that workspace).\n\n#### Signature\n\n```http\nGET /workspace/home/inbox (kind?: string) -> { data: Item[] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/home/tasks":{"get":{"operationId":"WorkspaceController_myTasks","summary":"My tasks","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated statuses.","example":"new,inprogress"}],"responses":{"200":{"description":"Task[]","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","example":"inprogress"},"dueDate":{"type":"string","nullable":true},"assignTo":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"}}}},"assignedBy":{"type":"string"},"history":{"type":"array","items":{"type":"object","additionalProperties":true}},"workspace":{"type":"string"},"workspaceTitle":{"type":"string"},"item":{"type":"string","nullable":true,"description":"The journal item that carries the task."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"room":{"type":"object","nullable":true},"agenda":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"title":{"type":"string"}}},"overdue":{"type":"boolean"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Tasks in every joined workspace that are assigned to the caller or that the caller created, newest first, in the same shape as a workspace board.\n\n#### Signature\n\n```http\nGET /workspace/home/tasks (status?: string) -> Task[]\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/home/calendar":{"get":{"operationId":"WorkspaceController_myCalendar","summary":"My calendar","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date-time.","example":"2026-10-01T00:00:00Z"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date-time.","example":"2026-10-31T23:59:59Z"}],"responses":{"200":{"description":"{ events: Item[], meetings: Meeting[] }","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}},"meetings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"reservation sk"},"title":{"type":"string"},"status":{"type":"string","example":"confirmed"},"startTime":{"type":"string"},"endTime":{"type":"string"},"timezone":{"type":"string","nullable":true},"meetingLink":{"type":"string","nullable":true},"meetingInfo":{"type":"string"},"invites":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}}}},"item":{"type":"string","nullable":true},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"canceled":{"type":"boolean"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Events and meetings across joined workspaces. A meeting is listed once, as its reservation; `from`/`to` filter meetings by start time. Meetings are sorted by start.\n\n#### Signature\n\n```http\nGET /workspace/home/calendar (from?: string, to?: string) -> { events: Item[], meetings: Meeting[] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/home/files":{"get":{"operationId":"WorkspaceController_recentFiles","summary":"Recent files","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"workspace","required":false,"in":"query","schema":{"type":"string"},"description":"Only this workspace (must be readable)."},{"name":"by","required":false,"in":"query","schema":{"type":"string"},"description":"Only files posted by this email."},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Item[]","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/home/files","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"File items, and any item with attachments, across joined workspaces (or one workspace), newest first. Expired and deleted items are left out.\n\n#### Signature\n\n```http\nGET /workspace/home/files (workspace?: string, by?: string, limit?: integer) -> Item[]\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/home/search":{"get":{"operationId":"WorkspaceController_searchAll","summary":"Search everything I have joined","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"q","required":true,"in":"query","schema":{"type":"string"},"example":"launch plan"}],"responses":{"200":{"description":"{ items: Item[], tasks }","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}},"tasks":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Case-insensitive text match on item title, message and summary, and on task title and description, across joined workspaces. Up to 100 of each, newest first. An empty `q` returns nothing.\n\n#### Signature\n\n```http\nGET /workspace/home/search (q?: string) -> { items: Item[], tasks }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace":{"get":{"operationId":"WorkspaceController_list","summary":"List workspaces","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","schema":{"type":"string","enum":["workspace","conversation"]}},{"name":"joined","required":false,"in":"query","schema":{"type":"string","enum":["true","1"]},"description":"Only workspaces the caller is a member of."}],"responses":{"200":{"description":"Workspace summaries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string","enum":["workspace","conversation"]},"isPrivate":{"type":"boolean"},"direct":{"type":"boolean","description":"A direct conversation between a fixed set of people."},"status":{"type":"string","enum":["active","archived"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"memberCount":{"type":"number"},"isMember":{"type":"boolean"},"isAdmin":{"type":"boolean"},"author":{"type":"string"},"createdate":{"type":"string","format":"date-time"},"modifydate":{"type":"string","format":"date-time"},"lastActivityAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Link to the workspace in the app that owns it."},"unread":{"type":"number","description":"List views only: items by others since the caller last read it."}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Workspaces and conversations the caller can read, each with `unread`. `joined=true` keeps only the ones they are a member of; otherwise public ones they could join are included (never for external callers).\n\n#### Signature\n\n```http\nGET /workspace (type?: string, joined?: string) -> Workspace summaries\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"post":{"operationId":"WorkspaceController_create","summary":"Create a workspace or conversation","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"The workspace summary with `members` (and `existing: true` for a found direct conversation)","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string","enum":["workspace","conversation"]},"isPrivate":{"type":"boolean"},"direct":{"type":"boolean","description":"A direct conversation between a fixed set of people."},"status":{"type":"string","enum":["active","archived"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"memberCount":{"type":"number"},"isMember":{"type":"boolean"},"isAdmin":{"type":"boolean"},"author":{"type":"string"},"createdate":{"type":"string","format":"date-time"},"modifydate":{"type":"string","format":"date-time"},"lastActivityAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Link to the workspace in the app that owns it."},"unread":{"type":"number","description":"List views only: items by others since the caller last read it."}}}}}},"400":{"description":"<email> is not an email address. Members are users or customers, never groups. — A member that is not an email (body `reason: \"not-a-person\"`).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"<email> is not an email address. Members are users or customers, never groups.","path":"/workspace","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"External members cannot create workspaces — The caller is a customer, an invited outsider or a user flagged external.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"External members cannot create workspaces","path":"/workspace","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Creates a workspace (`type` omitted) or a conversation (`type: \"conversation\"`). The caller becomes its admin. `members` are added as members — people with no account are invited and receive a sign-up email that lands on `redirectUrl`; customers are always external.\n\n**Direct conversations** (`type: \"conversation\", direct: true`) are private and unique per set of people: if one already exists for exactly these members it comes back with `existing: true` instead of a new one. The title defaults to the other members' names.\n\n#### Signature\n\n```http\nPOST /workspace (body) -> The workspace summary with `members` (and `existing: true` for a found direct conversation)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | EXTERNAL_CANNOT_CREATE | External members cannot create workspaces | The caller is a customer, an invited outsider or a user flagged external. | — |\n| `400` | NOT_A_PERSON | <email> is not an email address. Members are users or customers, never groups. | A member that is not an email (body `reason: \"not-a-person\"`). | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string","description":"Slug; derived from the title when omitted."},"description":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string","enum":["workspace","conversation"]},"direct":{"type":"boolean"},"isPrivate":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"appUrl":{"type":"string","description":"Where links in notification emails open."},"members":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"object","properties":{"email":{"type":"string"},"external":{"type":"boolean"},"expiresAt":{"type":"string"}}}]}},"redirectUrl":{"type":"string","description":"Where an invited outsider lands to finish signing up."}}},"examples":{"workspace":{"value":{"title":"Q4 launch","isPrivate":true,"members":["ana@acme.com"]}},"direct":{"value":{"type":"conversation","direct":true,"members":["ana@acme.com"]}}}}}}}},"/workspace/digest/run":{"post":{"operationId":"WorkspaceController_digest","summary":"Send the away digests now","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"graceMinutes","required":false,"in":"query","schema":{"type":"integer"},"example":0}],"responses":{"201":{"description":"{ sent }","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Runs what the 15-minute job does for this org: emails members the unread activity they missed. `graceMinutes` (default 30) skips activity newer than that; `0` includes everything unread.\n\n#### Signature\n\n```http\nPOST /workspace/digest/run (graceMinutes?: integer) -> { sent }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/migrate":{"post":{"operationId":"WorkspaceController_migrate","summary":"Migrate existing workspaces","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"{ workspaces, items } — how many were changed","content":{"application/json":{"schema":{"type":"object","properties":{"workspaces":{"type":"number"},"items":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Idempotent backfill: gives workspaces without one a `type` (workspace) and `status` (active), and links each item to its workspace, typing unknown items as `file` or `message`.\n\n#### Signature\n\n```http\nPOST /workspace/migrate () -> { workspaces, items } — how many were changed\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/item/{iid}":{"get":{"operationId":"WorkspaceController_getItem","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"iid","required":true,"in":"path","schema":{"type":"string"},"description":"workspace_item sk.","example":"66f1a2b3c4d5e6f7a8b9c0e2"}],"responses":{"200":{"description":"The item","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Item not found — No such item, or it has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Item not found","path":"/workspace/item/{iid}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Get an item","description":"#### Signature\n\n```http\nGET /workspace/item/{iid} (iid: string) -> The item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Item not found | No such item, or it has expired. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"patch":{"operationId":"WorkspaceController_patchItem","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"iid","required":true,"in":"path","schema":{"type":"string"},"description":"workspace_item sk.","example":"66f1a2b3c4d5e6f7a8b9c0e2"}],"responses":{"200":{"description":"The item","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the author or an admin can edit this — The caller did not write the item and is not an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the author or an admin can edit this","path":"/workspace/item/{iid}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Item not found — No such item, or it has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Item not found","path":"/workspace/item/{iid}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Edit an item","description":"The author or a workspace admin edits `title`, `summary`, `message` (mentions are re-read from it), `files`, `expiresAt`, `status`, `room`, `lead` and `dueDate`. The item is marked `edited`.\n\n#### Signature\n\n```http\nPATCH /workspace/item/{iid} (iid: string, body) -> The item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Item not found | No such item, or it has expired. | — |\n| `403` | NOT_AUTHOR | Only the author or an admin can edit this | The caller did not write the item and is not an admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string"},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"expiresAt":{"type":"string"},"status":{"type":"string"},"room":{"type":"object","additionalProperties":true},"lead":{"type":"string"},"dueDate":{"type":"string"}}},"example":{"message":"Updated: kickoff moves to Tuesday."}}}}},"delete":{"operationId":"WorkspaceController_deleteItem","summary":"Delete an item","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"iid","required":true,"in":"path","schema":{"type":"string"},"description":"workspace_item sk.","example":"66f1a2b3c4d5e6f7a8b9c0e2"}],"responses":{"200":{"description":"The tombstoned item","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the author or an admin can delete this — The caller did not write the item and is not an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the author or an admin can delete this","path":"/workspace/item/{iid}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Item not found — No such item, or it has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Item not found","path":"/workspace/item/{iid}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"A tombstone: the card stays in the timeline with `deleted: true`; its message, summary and files are cleared.\n\n#### Signature\n\n```http\nDELETE /workspace/item/{iid} (iid: string) -> The tombstoned item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Item not found | No such item, or it has expired. | — |\n| `403` | NOT_AUTHOR | Only the author or an admin can delete this | The caller did not write the item and is not an admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/workspace/{id}":{"get":{"operationId":"WorkspaceController_get","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"200":{"description":"Summary plus `members`, `pinnedItems` and `intakeForm`","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string","enum":["workspace","conversation"]},"isPrivate":{"type":"boolean"},"direct":{"type":"boolean","description":"A direct conversation between a fixed set of people."},"status":{"type":"string","enum":["active","archived"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"memberCount":{"type":"number"},"isMember":{"type":"boolean"},"isAdmin":{"type":"boolean"},"author":{"type":"string"},"createdate":{"type":"string","format":"date-time"},"modifydate":{"type":"string","format":"date-time"},"lastActivityAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Link to the workspace in the app that owns it."},"unread":{"type":"number","description":"List views only: items by others since the caller last read it."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Get a workspace","description":"#### Signature\n\n```http\nGET /workspace/{id} (id: string) -> Summary plus `members`, `pinnedItems` and `intakeForm`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"patch":{"operationId":"WorkspaceController_patch","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"200":{"description":"The workspace summary","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string","enum":["workspace","conversation"]},"isPrivate":{"type":"boolean"},"direct":{"type":"boolean","description":"A direct conversation between a fixed set of people."},"status":{"type":"string","enum":["active","archived"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"memberCount":{"type":"number"},"isMember":{"type":"boolean"},"isAdmin":{"type":"boolean"},"author":{"type":"string"},"createdate":{"type":"string","format":"date-time"},"modifydate":{"type":"string","format":"date-time"},"lastActivityAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Link to the workspace in the app that owns it."},"unread":{"type":"number","description":"List views only: items by others since the caller last read it."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an admin of this workspace can do that — The caller is neither the owner nor a member with accessType admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an admin of this workspace can do that","path":"/workspace/{id}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Update a workspace","description":"Admins change `title`, `description`, `icon`, `isPrivate`, `expiresAt`, `pinnedItems` and `intakeForm`. Other fields are ignored; an empty body returns the summary unchanged.\n\n#### Signature\n\n```http\nPATCH /workspace/{id} (id: string, body) -> The workspace summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | ADMIN_REQUIRED | Only an admin of this workspace can do that | The caller is neither the owner nor a member with accessType admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string"},"isPrivate":{"type":"boolean"},"expiresAt":{"type":"string"},"pinnedItems":{"type":"array","items":{"type":"object","additionalProperties":true}},"intakeForm":{"type":"object","additionalProperties":true}}},"example":{"title":"Q4 launch (final)"}}}}}},"/workspace/{id}/archive":{"post":{"operationId":"WorkspaceController_archive","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"The workspace summary","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string","enum":["workspace","conversation"]},"isPrivate":{"type":"boolean"},"direct":{"type":"boolean","description":"A direct conversation between a fixed set of people."},"status":{"type":"string","enum":["active","archived"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"memberCount":{"type":"number"},"isMember":{"type":"boolean"},"isAdmin":{"type":"boolean"},"author":{"type":"string"},"createdate":{"type":"string","format":"date-time"},"modifydate":{"type":"string","format":"date-time"},"lastActivityAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Link to the workspace in the app that owns it."},"unread":{"type":"number","description":"List views only: items by others since the caller last read it."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an admin of this workspace can do that — The caller is neither the owner nor a member with accessType admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an admin of this workspace can do that","path":"/workspace/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/archive","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Archive a workspace","description":"Admins only. Sets `status: archived`.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/archive (id: string) -> The workspace summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | ADMIN_REQUIRED | Only an admin of this workspace can do that | The caller is neither the owner nor a member with accessType admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /workspace/{id}/restore`"}},"/workspace/{id}/restore":{"post":{"operationId":"WorkspaceController_restore","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"The workspace summary","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"icon":{"type":"string"},"type":{"type":"string","enum":["workspace","conversation"]},"isPrivate":{"type":"boolean"},"direct":{"type":"boolean","description":"A direct conversation between a fixed set of people."},"status":{"type":"string","enum":["active","archived"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"memberCount":{"type":"number"},"isMember":{"type":"boolean"},"isAdmin":{"type":"boolean"},"author":{"type":"string"},"createdate":{"type":"string","format":"date-time"},"modifydate":{"type":"string","format":"date-time"},"lastActivityAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Link to the workspace in the app that owns it."},"unread":{"type":"number","description":"List views only: items by others since the caller last read it."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an admin of this workspace can do that — The caller is neither the owner nor a member with accessType admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an admin of this workspace can do that","path":"/workspace/{id}/restore","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/restore","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Restore an archived workspace","description":"Admins only. Sets `status: active`.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/restore (id: string) -> The workspace summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | ADMIN_REQUIRED | Only an admin of this workspace can do that | The caller is neither the owner nor a member with accessType admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/workspace/{id}/members":{"get":{"operationId":"WorkspaceController_members","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"200":{"description":"Members, each with a computed `expired`","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","invited","inactive","expired"]},"accessType":{"type":"string","enum":["admin","member","guest"]},"external":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean","description":"Computed: expiresAt has passed."},"invitedBy":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"lastReadAt":{"type":"string","format":"date-time"},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/members","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"List members","description":"#### Signature\n\n```http\nGET /workspace/{id}/members (id: string) -> Members, each with a computed `expired`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"post":{"operationId":"WorkspaceController_addMembers","summary":"Add members","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"All members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","invited","inactive","expired"]},"accessType":{"type":"string","enum":["admin","member","guest"]},"external":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean","description":"Computed: expiresAt has passed."},"invitedBy":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"lastReadAt":{"type":"string","format":"date-time"},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"No members given — No entry with an email.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No members given","path":"/workspace/{id}/members","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/members","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/members","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Any member can add people. Each entry is `{ email, name?, accessType?: admin|member|guest, external?, expiresAt? }`; an existing member is updated in place. People with no account are invited (status `invited`) and emailed a sign-up link that lands on `redirectUrl`; others are notified they were added. The body may also be the bare array or a single member object.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/members (id: string, body) -> All members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n| `400` | NO_MEMBERS | No members given | No entry with an email. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","additionalProperties":true}},"redirectUrl":{"type":"string"},"appUrl":{"type":"string"}}},"example":{"members":[{"email":"lee@partner.io","accessType":"guest","expiresAt":"2026-12-31T00:00:00Z"}],"redirectUrl":"https://app.acme.com/signup"}}}}}},"/workspace/{id}/members/{email}":{"patch":{"operationId":"WorkspaceController_patchMember","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Member email (case-insensitive).","example":"ana@acme.com"}],"responses":{"200":{"description":"All members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","invited","inactive","expired"]},"accessType":{"type":"string","enum":["admin","member","guest"]},"external":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean","description":"Computed: expiresAt has passed."},"invitedBy":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"lastReadAt":{"type":"string","format":"date-time"},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an admin of this workspace can do that — The caller is neither the owner nor a member with accessType admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an admin of this workspace can do that","path":"/workspace/{id}/members/{email}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/members/{email}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Change a member","description":"Admins change `accessType` and `expiresAt` (null clears it); the person is emailed whenever their access changes. Anyone may change their own `notify` and `mutedUntil`.\n\n#### Signature\n\n```http\nPATCH /workspace/{id}/members/{email} (id: string, email: string, body) -> All members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | ADMIN_REQUIRED | Only an admin of this workspace can do that | The caller is neither the owner nor a member with accessType admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"accessType":{"type":"string","enum":["admin","member","guest"]},"expiresAt":{"type":"string","nullable":true},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","nullable":true}}},"example":{"accessType":"admin"}}}}},"delete":{"operationId":"WorkspaceController_removeMember","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Member email (case-insensitive).","example":"ana@acme.com"}],"responses":{"200":{"description":"All remaining members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","invited","inactive","expired"]},"accessType":{"type":"string","enum":["admin","member","guest"]},"external":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean","description":"Computed: expiresAt has passed."},"invitedBy":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"lastReadAt":{"type":"string","format":"date-time"},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only an admin of this workspace can do that — The caller is neither the owner nor a member with accessType admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only an admin of this workspace can do that","path":"/workspace/{id}/members/{email}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/members/{email}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Remove a member","description":"Admins remove anyone except the owner; any member may remove themselves (same as leave).\n\n#### Signature\n\n```http\nDELETE /workspace/{id}/members/{email} (id: string, email: string) -> All remaining members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | ADMIN_REQUIRED | Only an admin of this workspace can do that | The caller is neither the owner nor a member with accessType admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/workspace/{id}/join":{"post":{"operationId":"WorkspaceController_join","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"All members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","invited","inactive","expired"]},"accessType":{"type":"string","enum":["admin","member","guest"]},"external":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean","description":"Computed: expiresAt has passed."},"invitedBy":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"lastReadAt":{"type":"string","format":"date-time"},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You have to be invited to this one — The workspace is private.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You have to be invited to this one","path":"/workspace/{id}/join","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/join","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Join a public workspace","description":"Adds the caller as a member. Already a member: returns the members unchanged.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/join (id: string) -> All members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | INVITE_ONLY | You have to be invited to this one | The workspace is private. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/workspace/{id}/leave":{"post":{"operationId":"WorkspaceController_leave","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"All remaining members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","invited","inactive","expired"]},"accessType":{"type":"string","enum":["admin","member","guest"]},"external":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean","description":"Computed: expiresAt has passed."},"invitedBy":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"lastReadAt":{"type":"string","format":"date-time"},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/leave","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/leave","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Leave a workspace","description":"#### Signature\n\n```http\nPOST /workspace/{id}/leave (id: string) -> All remaining members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/workspace/{id}/items":{"get":{"operationId":"WorkspaceController_items","summary":"Read the journal","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Item type, or `activity` for all."},{"name":"room","required":false,"in":"query","schema":{"type":"string"},"description":"Room id."},{"name":"before","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date-time; items created before it."},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Default 50, max 200."},{"name":"thread","required":false,"in":"query","schema":{"type":"string"},"description":"Item sk: that item and its replies."},{"name":"includeExpired","required":false,"in":"query","schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"{ data: Item[], total, hasMore }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}},"total":{"type":"number"},"hasMore":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/items","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Top-level items newest first (replies are not listed; use `thread`). No filter is the Activity view; `type` limits to one item type; `room` to one room; `before` pages back. `thread` returns one item and its replies. Expired items are hidden unless `includeExpired=true`.\n\n#### Signature\n\n```http\nGET /workspace/{id}/items (id: string, type?: string, room?: string, before?: string, limit?: integer, thread?: string, includeExpired?: string) -> { data: Item[], total, hasMore }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/item":{"post":{"operationId":"WorkspaceController_createItem","summary":"Post an item","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"x-client-info","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"The created item","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Member entries are written by the members routes — `type: \"member\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Member entries are written by the members routes","path":"/workspace/{id}/item","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/item","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/item","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"This org has no \"meeting\" reservation definition yet (run org initialization) — Posting a meeting before the org has a `meeting` reservation definition.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"This org has no \"meeting\" reservation definition yet (run org initialization)","path":"/workspace/{id}/item","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Members post any item type in one call: `message` (default), `file`, `task`, `event` / `meeting`, `agenda`, `analytics`, `data`, `team`, `block`.\n\n- **task**: creates the `task` record (title, `description`, `dueDate`, `assignTo`, optional `agenda` project) and links it; assignees are notified. Fields may be flat or under `task`.\n- **meeting** (or `event` with `meeting`): creates a reservation on the org's `meeting` definition (`startTime`, `endTime`, `timezone`, `meetingLink`, `meetingInfo`, `invites`).\n- **reply**: set `parentItem` to a top-level item in this workspace; replies are one level deep and inherit its room.\n\n@mentions in `message` (and any emails in `mentions`) are notified; direct-conversation members are notified of every message.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/item (id: string, body) -> The created item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n| `400` | MEMBER_ITEM | Member entries are written by the members routes | `type: \"member\"`. | — |\n| `422` | NO_MEETING_DEFINITION | This org has no \"meeting\" reservation definition yet (run org initialization) | Posting a meeting before the org has a `meeting` reservation definition. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"message":{"type":"string"},"summary":{"type":"string"},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"room":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string"},"expiresAt":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"}},"task":{"type":"object","additionalProperties":true},"meeting":{"type":"object","additionalProperties":true},"agenda":{"type":"string"},"lead":{"type":"string"},"dueDate":{"type":"string"}}},"examples":{"message":{"value":{"message":"Kickoff notes are up @ana@acme.com","room":{"label":"Design"}}},"task":{"value":{"type":"task","title":"Draft the brief","assignTo":["ana@acme.com"],"dueDate":"2026-10-15"}},"meeting":{"value":{"type":"meeting","title":"Kickoff","startTime":"2026-10-07T15:00:00Z","endTime":"2026-10-07T16:00:00Z","invites":["ana@acme.com"]}}}}}}}},"/workspace/{id}/rooms":{"get":{"operationId":"WorkspaceController_rooms","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"200":{"description":"[{ id, label, lastActivityAt, count }]","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/rooms","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"List rooms","description":"Rooms that messages in this workspace were posted to, most recently active first, with message counts.\n\n#### Signature\n\n```http\nGET /workspace/{id}/rooms (id: string) -> [{ id, label, lastActivityAt, count }]\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/tasks":{"get":{"operationId":"WorkspaceController_tasks","summary":"Task board","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated statuses."},{"name":"assignTo","required":false,"in":"query","schema":{"type":"string"},"description":"Assignee email."},{"name":"agenda","required":false,"in":"query","schema":{"type":"string"},"description":"Agenda item sk, or `none` for tasks in no project."}],"responses":{"200":{"description":"Task[]","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","example":"inprogress"},"dueDate":{"type":"string","nullable":true},"assignTo":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"}}}},"assignedBy":{"type":"string"},"history":{"type":"array","items":{"type":"object","additionalProperties":true}},"workspace":{"type":"string"},"workspaceTitle":{"type":"string"},"item":{"type":"string","nullable":true,"description":"The journal item that carries the task."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"room":{"type":"object","nullable":true},"agenda":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"title":{"type":"string"}}},"overdue":{"type":"boolean"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/tasks","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"The workspace's tasks (up to 500) as the board shows them, with assignee names, files and room from the journal card, the agenda (project) and `overdue`.\n\n#### Signature\n\n```http\nGET /workspace/{id}/tasks (id: string, status?: string, assignTo?: string, agenda?: string) -> Task[]\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/meetings/{mid}":{"get":{"operationId":"WorkspaceController_getMeeting","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"mid","required":true,"in":"path","schema":{"type":"string"},"description":"reservation sk"}],"responses":{"200":{"description":"The meeting","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"reservation sk"},"title":{"type":"string"},"status":{"type":"string","example":"confirmed"},"startTime":{"type":"string"},"endTime":{"type":"string"},"timezone":{"type":"string","nullable":true},"meetingLink":{"type":"string","nullable":true},"meetingInfo":{"type":"string"},"invites":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}}}},"item":{"type":"string","nullable":true},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"canceled":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/meetings/{mid}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Get a meeting","description":"#### Signature\n\n```http\nGET /workspace/{id}/meetings/{mid} (id: string, mid: string) -> The meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"patch":{"operationId":"WorkspaceController_patchMeeting","summary":"Edit or cancel a meeting","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"mid","required":true,"in":"path","schema":{"type":"string"},"description":"reservation sk"}],"responses":{"200":{"description":"The meeting","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"reservation sk"},"title":{"type":"string"},"status":{"type":"string","example":"confirmed"},"startTime":{"type":"string"},"endTime":{"type":"string"},"timezone":{"type":"string","nullable":true},"meetingLink":{"type":"string","nullable":true},"meetingInfo":{"type":"string"},"invites":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}}}},"item":{"type":"string","nullable":true},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"canceled":{"type":"boolean"}}}}}},"400":{"description":"The meeting has to end after it starts — `endTime` is not after `startTime`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The meeting has to end after it starts","path":"/workspace/{id}/meetings/{mid}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/meetings/{mid}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/meetings/{mid}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Members change `title`, `startTime`, `endTime`, `timezone`, `meetingLink`, `meetingInfo` and `invites` (existing invitees keep their response); `cancel: true` cancels it. Moving the start past the current end pushes the end to one hour after the start. The journal card follows.\n\n#### Signature\n\n```http\nPATCH /workspace/{id}/meetings/{mid} (id: string, mid: string, body) -> The meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n| `400` | BAD_TIMES | The meeting has to end after it starts | `endTime` is not after `startTime`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"startTime":{"type":"string"},"endTime":{"type":"string"},"timezone":{"type":"string"},"meetingLink":{"type":"string"},"meetingInfo":{"type":"string"},"invites":{"type":"array","items":{"type":"string"}},"cancel":{"type":"boolean"}}},"example":{"startTime":"2026-10-08T15:00:00Z"}}}}}},"/workspace/{id}/tasks/{tid}":{"get":{"operationId":"WorkspaceController_getTask","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"tid","required":true,"in":"path","schema":{"type":"string"},"description":"task sk"}],"responses":{"200":{"description":"The task","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","example":"inprogress"},"dueDate":{"type":"string","nullable":true},"assignTo":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"}}}},"assignedBy":{"type":"string"},"history":{"type":"array","items":{"type":"object","additionalProperties":true}},"workspace":{"type":"string"},"workspaceTitle":{"type":"string"},"item":{"type":"string","nullable":true,"description":"The journal item that carries the task."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"room":{"type":"object","nullable":true},"agenda":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"title":{"type":"string"}}},"overdue":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/tasks/{tid}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Get a task","description":"#### Signature\n\n```http\nGET /workspace/{id}/tasks/{tid} (id: string, tid: string) -> The task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"patch":{"operationId":"WorkspaceController_patchTask","summary":"Edit a task","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"tid","required":true,"in":"path","schema":{"type":"string"},"description":"task sk"}],"responses":{"200":{"description":"The task","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","example":"inprogress"},"dueDate":{"type":"string","nullable":true},"assignTo":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"}}}},"assignedBy":{"type":"string"},"history":{"type":"array","items":{"type":"object","additionalProperties":true}},"workspace":{"type":"string"},"workspaceTitle":{"type":"string"},"item":{"type":"string","nullable":true,"description":"The journal item that carries the task."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"room":{"type":"object","nullable":true},"agenda":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"title":{"type":"string"}}},"overdue":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/tasks/{tid}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/tasks/{tid}","method":"PATCH","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"Members change `title`, `description`, `status` (done/approved stamps `completedAt`), `dueDate`, `assignTo` and `agenda` (null removes it). Each change is appended to the task history and mirrored on its journal card; newly assigned people are notified. No change returns the task as is.\n\n#### Signature\n\n```http\nPATCH /workspace/{id}/tasks/{tid} (id: string, tid: string, body) -> The task\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string"},"dueDate":{"type":"string","nullable":true},"assignTo":{"type":"array","items":{"type":"string"}},"agenda":{"type":"string","nullable":true}}},"example":{"status":"done"}}}}}},"/workspace/{id}/files":{"get":{"operationId":"WorkspaceController_files","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"200":{"description":"Item[]","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/files","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Workspace files","description":"The workspace's `file` items, newest first, up to 200.\n\n#### Signature\n\n```http\nGET /workspace/{id}/files (id: string) -> Item[]\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/calendar":{"get":{"operationId":"WorkspaceController_calendar","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"from","required":false,"in":"query","schema":{"type":"string"}},{"name":"to","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ events: Item[], meetings: Meeting[] }","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}},"meetings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"reservation sk"},"title":{"type":"string"},"status":{"type":"string","example":"confirmed"},"startTime":{"type":"string"},"endTime":{"type":"string"},"timezone":{"type":"string","nullable":true},"meetingLink":{"type":"string","nullable":true},"meetingInfo":{"type":"string"},"invites":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}}}},"item":{"type":"string","nullable":true},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"canceled":{"type":"boolean"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/calendar","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Workspace calendar","description":"Event items and meetings (reservations) of this workspace; `from`/`to` filter meetings by start time.\n\n#### Signature\n\n```http\nGET /workspace/{id}/calendar (id: string, from?: string, to?: string) -> { events: Item[], meetings: Meeting[] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/agenda":{"get":{"operationId":"WorkspaceController_agenda","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"200":{"description":"Agenda items with progress","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"workspace":{"type":"string"},"type":{"type":"string","enum":["message","task","file","event","agenda","analytics","member","data","team","block"]},"title":{"type":"string"},"summary":{"type":"string"},"message":{"type":"string","description":"Empty once deleted."},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Linked records, e.g. `{ datatype: \"task\", id, title }` or `{ datatype: \"reservation\", id }`."},"room":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"parentItem":{"type":"string","nullable":true},"replyCount":{"type":"number"},"mentions":{"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","nullable":true},"edited":{"type":"boolean"},"editedAt":{"type":"string"},"deleted":{"type":"boolean"},"status":{"type":"string"},"agenda":{"type":"string","nullable":true},"lead":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true},"author":{"type":"string"},"authorName":{"type":"string"},"createdate":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/agenda","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Agendas (projects)","description":"The workspace's `agenda` items, each with `leadName` and `progress: { total, done, open, overdue }` counted from the tasks linked to it.\n\n#### Signature\n\n```http\nGET /workspace/{id}/agenda (id: string) -> Agenda items with progress\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/analytics":{"get":{"operationId":"WorkspaceController_analytics","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"200":{"description":"{ tasks: { total, byStatus, byAssignee, overdue }, items: { total, byType, perWeek }, members: { total, activeLast30Days } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/analytics","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Workspace analytics","description":"Counts for the workspace: tasks by status and assignee plus overdue; items by type and per ISO week; members and how many posted in the last 30 days.\n\n#### Signature\n\n```http\nGET /workspace/{id}/analytics (id: string) -> { tasks: { total, byStatus, byAssignee, overdue }, items: { total, byType, perWeek }, members: { total, activeLast30Days } }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/search":{"get":{"operationId":"WorkspaceController_search","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"q","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ items: Item[], tasks }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/search","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Search a workspace","description":"Case-insensitive match on item title/message/summary and task title/description in this workspace. Up to 100 of each.\n\n#### Signature\n\n```http\nGET /workspace/{id}/search (id: string, q?: string) -> { items: Item[], tasks }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/read":{"post":{"operationId":"WorkspaceController_read","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"{ read, at? }","content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean"},"at":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/read","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Mark as read","description":"Stamps the caller's `lastReadAt`, which clears their unread count. A reader who is not a member gets `{ read: false }`.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/read (id: string) -> { read, at? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/workspace/{id}/pin/{iid}":{"post":{"operationId":"WorkspaceController_pin","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"iid","required":true,"in":"path","schema":{"type":"string"},"description":"workspace_item sk.","example":"66f1a2b3c4d5e6f7a8b9c0e2"}],"responses":{"201":{"description":"The pinned items","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/pin/{iid}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/pin/{iid}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Pin an item","description":"Members pin an item to the workspace.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/pin/{iid} (id: string, iid: string) -> The pinned items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n\nPlus the standard platform errors: `401`, `429`, `500`."},"delete":{"operationId":"WorkspaceController_unpin","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"},{"name":"iid","required":true,"in":"path","schema":{"type":"string"},"description":"workspace_item sk.","example":"66f1a2b3c4d5e6f7a8b9c0e2"}],"responses":{"200":{"description":"The pinned items","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/pin/{iid}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/pin/{iid}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"summary":"Unpin an item","description":"#### Signature\n\n```http\nDELETE /workspace/{id}/pin/{iid} (id: string, iid: string) -> The pinned items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/workspace/{id}/notify":{"post":{"operationId":"WorkspaceController_notify","summary":"My notification setting","parameters":[{"name":"orgid","in":"header","required":true,"schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Workspace sk.","example":"66f1a2b3c4d5e6f7a8b9c0d1"}],"responses":{"201":{"description":"All members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","invited","inactive","expired"]},"accessType":{"type":"string","enum":["admin","member","guest"]},"external":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean","description":"Computed: expiresAt has passed."},"invitedBy":{"type":"string"},"joinedAt":{"type":"string","format":"date-time"},"lastReadAt":{"type":"string","format":"date-time"},"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Join this workspace to post in it — The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Join this workspace to post in it","path":"/workspace/{id}/notify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Workspace not found — No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Workspace not found","path":"/workspace/{id}/notify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Workspace"],"description":"The caller's own notification level for this workspace and an optional mute end.\n\n#### Signature\n\n```http\nPOST /workspace/{id}/notify (id: string, body) -> All members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | WORKSPACE_NOT_FOUND | Workspace not found | No such workspace, it has expired, or the caller cannot read it (private, or external caller on a public one). | — |\n| `403` | JOIN_REQUIRED | Join this workspace to post in it | The caller can read the workspace but is not a member. Body also carries `reason: \"join-required\"`. | POST /workspace/{id}/join (public workspaces) or ask an admin to add you. |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"notify":{"type":"string","enum":["all","mentions","none"]},"mutedUntil":{"type":"string","format":"date-time"}}},"example":{"notify":"mentions"}}}}}},"/checkin/walk-in":{"post":{"operationId":"CheckinController_walkIn","summary":"Check in a walk-in guest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The arriving guest.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["customer"],"properties":{"businessLocationId":{"type":"string","example":"LOC-3"},"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"preferences":{"type":"object","additionalProperties":true,"example":{"seating":"booth"}},"notes":{"type":"string","example":"Celebrating a birthday"}}},"example":{"businessLocationId":"LOC-3","customer":{"name":"Ada Lovelace","phone":"+15551234567"},"partySize":4,"notes":"Celebrating a birthday"}}}},"responses":{"201":{"description":"The queue entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"customer is required (name/email/phone) — `customer` is missing or has none of name, email or phone.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"customer is required (name/email/phone)","path":"/checkin/walk-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to create check-in task — The pipeline task could not be created.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to create check-in task","path":"/checkin/walk-in","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Check-in"],"description":"Creates a queue entry for a guest who arrived without a reservation. No Reservation record is created or needed — the queue entry is the Task, and a walk-in simply starts at stage 0 of the check-in pipeline.\n\nAt least one of name, email or phone must be present in `customer`; the contact details are what the \"your table is ready\" notification is later sent to.\n\n#### Signature\n\n```http\nPOST /checkin/walk-in (body) -> The queue entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CUSTOMER_REQUIRED | customer is required (name/email/phone) | `customer` is missing or has none of name, email or phone. | Supply at least one contact detail. |\n| `500` | TASK_CREATE_FAILED | Failed to create check-in task | The pipeline task could not be created. | Retry; if it persists the check-in pipeline may not exist for this org. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /checkin/from-reservation/{reservationId}`\n- `GET /checkin/queue`"}},"/checkin/from-reservation/{reservationId}":{"post":{"operationId":"CheckinController_fromReservation","summary":"Check in a reserved guest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"reservationId","required":true,"in":"path","schema":{"type":"string"},"description":"The reservation being honoured.","example":"RES-771"}],"responses":{"201":{"description":"The queue entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Reservation not found — No reservation has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Reservation not found","path":"/checkin/from-reservation/{reservationId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to create check-in task — The pipeline task could not be created.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to create check-in task","path":"/checkin/from-reservation/{reservationId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Check-in"],"description":"Creates a queue entry linked to an existing reservation — the guest who booked has now physically arrived. The reservation stays as it was; the queue entry is a new Task that carries the reservation's party and contact details forward.\n\nCalling it twice creates a second queue entry for the same reservation — check the queue before re-checking someone in.\n\n#### Signature\n\n```http\nPOST /checkin/from-reservation/{reservationId} (reservationId: string, body) -> The queue entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent — repeated calls queue the guest twice.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | RESERVATION_NOT_FOUND | Reservation not found | No reservation has that id. | Check the id, or use `POST /checkin/walk-in`. |\n| `500` | TASK_CREATE_FAILED | Failed to create check-in task | The pipeline task could not be created. | Retry. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `GET /checkin/upcoming`\n- `GET /checkin/reservations/today`","requestBody":{"description":"Overrides — party size, notes, location.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"partySize":5,"notes":"Two extra guests"}}}}}},"/checkin/{taskId}/assign":{"post":{"operationId":"CheckinController_assign","summary":"Assign a service point","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Check-in task id.","example":"TASK-4821"}],"requestBody":{"description":"Which service point.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["servicePointId"],"properties":{"servicePointId":{"type":"string","example":"SP-12"}}},"example":{"servicePointId":"SP-12"}}}},"responses":{"201":{"description":"The seated entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"servicePointId required — `servicePointId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"servicePointId required","path":"/checkin/{taskId}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Check-in task not found — No check-in task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Check-in task not found","path":"/checkin/{taskId}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Service point not available — The service point is occupied, dirty, closed or reserved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Service point not available","path":"/checkin/{taskId}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Seats the guest: marks the service point occupied, advances the task to its terminal stage — removing it from the live queue — and **notifies the customer** that their table is ready.\n\nThe notification goes out on the contact details captured at check-in, so this is an outward-facing action. A task already assigned is refused rather than re-notified.\n\n#### Signature\n\n```http\nPOST /checkin/{taskId}/assign (taskId: string, body) -> The seated entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends a notification to the guest.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Check-in task not found | No check-in task has that id. | Read the live queue with `GET /checkin/queue`. |\n| `400` | SERVICE_POINT_REQUIRED | servicePointId required | `servicePointId` is missing. | Supply a service point id. |\n| `409` | SERVICE_POINT_UNAVAILABLE | Service point not available | The service point is occupied, dirty, closed or reserved. | Pick an open one from `GET /checkin/service-point/available`, or clear it first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/service-point/available`\n- `POST /checkin/service-point/{spId}/clear`"}},"/checkin/{taskId}/leave":{"post":{"operationId":"CheckinController_leave","summary":"Guest left the queue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Check-in task id.","example":"TASK-4821"}],"responses":{"201":{"description":"The closed entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Check-in task not found — No check-in task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Check-in task not found","path":"/checkin/{taskId}/leave","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Removes a guest who gave up waiting. Distinct from a no-show: they arrived and then left, which is a wait-time problem rather than a booking one, and the two are counted separately in queue history.\n\n#### Signature\n\n```http\nPOST /checkin/{taskId}/leave (taskId: string, body) -> The closed entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Check-in task not found | No check-in task has that id. | Read the live queue with `GET /checkin/queue`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /checkin/{taskId}/no-show`","requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Wait too long"}}},"example":{"reason":"Wait too long"}}}}}},"/checkin/{taskId}/no-show":{"post":{"operationId":"CheckinController_noShow","summary":"Mark a guest as a no-show","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Check-in task id.","example":"TASK-4821"}],"responses":{"201":{"description":"The closed entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Check-in task not found — No check-in task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Check-in task not found","path":"/checkin/{taskId}/no-show","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Closes a queue entry for a guest who never turned up — typically a reservation check-in created ahead of arrival. Kept distinct from \"left\" so no-show rates stay meaningful.\n\n#### Signature\n\n```http\nPOST /checkin/{taskId}/no-show (taskId: string) -> The closed entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Check-in task not found | No check-in task has that id. | Read the live queue with `GET /checkin/queue`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /checkin/{taskId}/leave`"}},"/checkin/{taskId}/notify":{"post":{"operationId":"CheckinController_notify","summary":"Re-send the ready notification","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Check-in task id.","example":"TASK-4821"}],"responses":{"201":{"description":"The send result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Check-in task not found — No check-in task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Check-in task not found","path":"/checkin/{taskId}/notify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Sends the \"your table is ready\" message again — for a guest who did not see the first one. This sends a real message to the guest each time it is called; there is no rate limit here.\n\n#### Signature\n\n```http\nPOST /checkin/{taskId}/notify (taskId: string) -> The send result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Outward-facing: every call messages the guest.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Check-in task not found | No check-in task has that id. | Read the live queue with `GET /checkin/queue`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /checkin/{taskId}/assign`"}},"/checkin/service-point/{spId}/clear":{"post":{"operationId":"CheckinController_clearServicePoint","summary":"Clear a service point","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"spId","required":true,"in":"path","schema":{"type":"string"},"description":"Service point id.","example":"SP-12"}],"responses":{"201":{"description":"The cleared service point","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Service point not found — No service point has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Service point not found","path":"/checkin/service-point/{spId}/clear","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Frees a service point after a party leaves. `finalStatus` decides what it becomes: `dirty` when it still needs bussing, `available` when it is ready for the next guest — only `available` points can be assigned.\n\n#### Signature\n\n```http\nPOST /checkin/service-point/{spId}/clear (spId: string, body) -> The cleared service point\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SERVICE_POINT_NOT_FOUND | Service point not found | No service point has that id. | List them with `GET /checkin/service-point`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/service-point/available`","requestBody":{"description":"The status to leave it in.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"finalStatus":{"type":"string","enum":["dirty","available"],"description":"Default is the configured post-service status.","example":"dirty"}}},"examples":{"bussing":{"summary":"Needs bussing first","value":{"finalStatus":"dirty"}},"ready":{"summary":"Ready for the next party","value":{"finalStatus":"available"}}}}}}}},"/checkin/service-point/available":{"get":{"operationId":"CheckinController_listAvailableServicePoints","summary":"List available service points","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one location.","example":"LOC-3"}],"responses":{"200":{"description":"Available service points","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Only the service points that can be assigned right now — the host's \"what's open\" view. Excludes occupied, dirty, closed and reserved.\n\n#### Signature\n\n```http\nGET /checkin/service-point/available (businessLocationId?: string) -> Available service points\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/service-point`"}},"/checkin/service-point":{"get":{"operationId":"CheckinController_listAllServicePoints","summary":"List all service points","description":"The full floor view: every service point at a location in any status — open, occupied, dirty, closed, reserved. Occupied and reserved points are enriched with the check-in task currently assigned to them (customer name, party size, `seatedAt`, `seatedMs`), so a floor plan renders from this one call.\n\n#### Signature\n\n```http\nGET /checkin/service-point (businessLocationId?: string) -> All service points, enriched where occupied\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/service-point/available`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one location.","example":"LOC-3"}],"responses":{"200":{"description":"All service points, enriched where occupied","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"]}},"/checkin/queue":{"get":{"operationId":"CheckinController_listQueue","summary":"Get the live queue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one location.","example":"LOC-3"},{"name":"partySize","required":false,"in":"query","schema":{"type":"integer"},"description":"Filter by party size.","example":4},{"name":"source","required":false,"in":"query","schema":{"type":"string"},"description":"`walk-in` or `reservation`.","example":"walk-in"}],"responses":{"200":{"description":"Waiting guests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Everyone currently waiting — the tasks still at stage 0 of the check-in pipeline. Seated, left, no-show and cancelled entries are not here; they are in the queue history.\n\n#### Signature\n\n```http\nGET /checkin/queue (businessLocationId?: string, partySize?: integer, source?: string) -> Waiting guests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/queue/summary`\n- `GET /checkin/queue/history`"}},"/checkin/queue/history":{"get":{"operationId":"CheckinController_queueHistory","summary":"Get queue history","description":"Entries that have left the live queue — assigned, completed, no-show, left or cancelled. The \"what came through earlier\" view.\n\n`sinceMs` is a **duration in milliseconds looking back from now**, not a timestamp: `86400000` is the last 24 hours.\n\n#### Signature\n\n```http\nGET /checkin/queue/history (businessLocationId?: string, sinceMs?: integer, pageSize?: integer) -> Past queue entries\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/queue`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one location.","example":"LOC-3"},{"name":"sinceMs","required":false,"in":"query","schema":{"type":"integer"},"description":"Look-back window in milliseconds. `86400000` = last 24 hours.","example":86400000},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":100}],"responses":{"200":{"description":"Past queue entries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"]}},"/checkin/queue/summary":{"get":{"operationId":"CheckinController_queueSummary","summary":"Get a queue summary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one location.","example":"LOC-3"}],"responses":{"200":{"description":"The summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Headline queue figures — how many are waiting, the current average wait and the longest party currently waiting. The one call a status board needs.\n\n#### Signature\n\n```http\nGET /checkin/queue/summary (businessLocationId?: string) -> The summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/queue`"}},"/checkin/upcoming":{"get":{"operationId":"CheckinController_upcoming","summary":"List upcoming reservations","description":"Reservations expected within the next N hours that have **not** yet been checked in — who the host should be watching the door for. Once a reservation is checked in it drops out of this list and appears in the live queue.\n\n#### Signature\n\n```http\nGET /checkin/upcoming (businessLocationId?: string, windowHours?: number) -> Expected reservations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /checkin/from-reservation/{reservationId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one location.","example":"LOC-3"},{"name":"windowHours","required":false,"in":"query","schema":{"type":"number"},"description":"Look-ahead window in hours.","example":2}],"responses":{"200":{"description":"Expected reservations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"]}},"/checkin/reservations/today":{"get":{"operationId":"CheckinController_reservationsToday","summary":"List today's reservations","description":"Every reservation for the whole calendar day in any status — the check-in page's \"Today\" view, unlike `upcoming` which is a forward window of not-yet-arrived guests.\n\nPass `tzOffsetMinutes` (the browser's `new Date().getTimezoneOffset()`) so the day boundary matches the operator's timezone rather than the server's — without it a late-evening reservation can land on the wrong day.\n\n#### Signature\n\n```http\nGET /checkin/reservations/today (businessLocationId?: string, tzOffsetMinutes?: integer) -> Today's reservations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Omitting `tzOffsetMinutes` uses the server day boundary.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/upcoming`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"businessLocationId","required":false,"in":"query","schema":{"type":"string"},"description":"Limit to one location.","example":"LOC-3"},{"name":"tzOffsetMinutes","required":false,"in":"query","schema":{"type":"integer"},"description":"The operator's UTC offset in minutes, as returned by `Date.prototype.getTimezoneOffset()`.","example":300}],"responses":{"200":{"description":"Today's reservations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"]}},"/checkin/queue/position/{taskId}":{"get":{"operationId":"CheckinController_position","summary":"Get queue position and ETA","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Check-in task id.","example":"TASK-4821"}],"responses":{"200":{"description":"Position and estimated wait","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Check-in task not found — No check-in task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Check-in task not found","path":"/checkin/queue/position/{taskId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"How far up the queue a party is and roughly how long they still have to wait — the answer to \"how long?\" at the host stand. The ETA is projected from recent throughput, so treat it as an estimate rather than a promise to the guest.\n\n#### Signature\n\n```http\nGET /checkin/queue/position/{taskId} (taskId: string) -> Position and estimated wait\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The ETA is a projection from recent seating rates.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Check-in task not found | No check-in task has that id. | Read the live queue with `GET /checkin/queue`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/queue/summary`"}},"/checkin/queue/{taskId}":{"get":{"operationId":"CheckinController_getQueueEntry","summary":"Get one queue entry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Check-in task id.","example":"TASK-4821"}],"responses":{"200":{"description":"The queue entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sk":{"type":"string","example":"TASK-4821"},"data":{"type":"object","additionalProperties":true,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"partySize":{"type":"number","example":4},"source":{"type":"string","enum":["walk-in","reservation"],"example":"walk-in"},"stageId":{"type":"integer","description":"0 while waiting in the live queue.","example":0},"status":{"type":"string","example":"pending"},"servicePointId":{"type":"string","example":"SP-12"},"seatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Check-in task not found — No check-in task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Check-in task not found","path":"/checkin/queue/{taskId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Check-in"],"description":"Fetches a single check-in entry with its customer, party and status.\n\n#### Signature\n\n```http\nGET /checkin/queue/{taskId} (taskId: string) -> The queue entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TASK_NOT_FOUND | Check-in task not found | No check-in task has that id. | Read the live queue with `GET /checkin/queue`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /checkin/queue/position/{taskId}`"}},"/stowbo/platform-fees":{"get":{"operationId":"StowboController_getPlatformFees","summary":"Get the platform fee list","description":"Returns the platform's own fees — the rows the platform adds to every bill. They are `stowbo_fee` records of type `platform`, `tax` or `processing` that are not `inactive`, symmetric with a host's listing fees: any number, of any type.\n\nThe list is cached for 30 seconds per node because pricing reads it on a hot path, so a change made through `POST` can take that long to appear on another node.\n\n#### Signature\n\n```http\nGET /stowbo/platform-fees () -> The active platform fees, normalised. Empty when none are set.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A failed read yields `[]` rather than an error — pricing must not fail because fees are unconfigured.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/platform-fees`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The active platform fees, normalised. Empty when none are set.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Key of the fee. `POST` matches existing fees by `name`; defaults to `code`, then `fee`.","example":"service"},"code":{"type":"string","description":"Machine code. Defaults to `name`.","example":"service"},"label":{"type":"string","description":"Shown on the bill. Read from `title`, `label` or `name`.","example":"Service fee"},"type":{"type":"string","enum":["platform","tax","processing"],"default":"platform","example":"platform"},"paidBy":{"type":"string","enum":["guest","host"],"default":"guest","description":"Who pays it. Anything other than `host` is `guest`."},"trigger":{"type":"string","default":"booking","description":"When it applies.","example":"booking"},"appliesTo":{"type":"string","enum":["checkout","item"],"description":"`checkout` once per bill, `item` once per thing booked. Legacy `per: unit` maps to `item`.","example":"checkout"},"basis":{"type":"string","enum":["fixed","percent"],"description":"`fixed` amount, or a `percent` of the goods. Legacy `per: percent` maps to `percent`.","example":"percent"},"amount":{"type":"number","default":0,"example":8},"priceFrom":{"type":"number","description":"Only for bills from this amount."},"priceTo":{"type":"number","description":"Only for bills up to this amount."},"minFee":{"type":"number"},"maxFee":{"type":"number"},"channels":{"type":"array","items":{"type":"string"},"description":"Booking channels it applies to. Empty = all."},"spaceTypes":{"type":"array","items":{"type":"string"},"description":"Space types it applies to. Empty = all."},"validFrom":{"type":"string","format":"date-time"},"validTo":{"type":"string","format":"date-time"},"taxable":{"type":"boolean","default":true,"description":"Only an explicit `false` turns it off."},"refundable":{"type":"boolean","default":false},"sortOrder":{"type":"number","default":100},"status":{"type":"string","example":"active"}}}},"example":[{"name":"service","code":"service","label":"Service fee","type":"platform","paidBy":"guest","trigger":"booking","appliesTo":"checkout","basis":"percent","amount":8,"channels":[],"spaceTypes":[],"taxable":true,"refundable":false,"sortOrder":100,"status":"active"}]}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Fees"]},"post":{"operationId":"StowboController_setPlatformFees","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The stored, normalised fee list","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Key of the fee. `POST` matches existing fees by `name`; defaults to `code`, then `fee`.","example":"service"},"code":{"type":"string","description":"Machine code. Defaults to `name`.","example":"service"},"label":{"type":"string","description":"Shown on the bill. Read from `title`, `label` or `name`.","example":"Service fee"},"type":{"type":"string","enum":["platform","tax","processing"],"default":"platform","example":"platform"},"paidBy":{"type":"string","enum":["guest","host"],"default":"guest","description":"Who pays it. Anything other than `host` is `guest`."},"trigger":{"type":"string","default":"booking","description":"When it applies.","example":"booking"},"appliesTo":{"type":"string","enum":["checkout","item"],"description":"`checkout` once per bill, `item` once per thing booked. Legacy `per: unit` maps to `item`.","example":"checkout"},"basis":{"type":"string","enum":["fixed","percent"],"description":"`fixed` amount, or a `percent` of the goods. Legacy `per: percent` maps to `percent`.","example":"percent"},"amount":{"type":"number","default":0,"example":8},"priceFrom":{"type":"number","description":"Only for bills from this amount."},"priceTo":{"type":"number","description":"Only for bills up to this amount."},"minFee":{"type":"number"},"maxFee":{"type":"number"},"channels":{"type":"array","items":{"type":"string"},"description":"Booking channels it applies to. Empty = all."},"spaceTypes":{"type":"array","items":{"type":"string"},"description":"Space types it applies to. Empty = all."},"validFrom":{"type":"string","format":"date-time"},"validTo":{"type":"string","format":"date-time"},"taxable":{"type":"boolean","default":true,"description":"Only an explicit `false` turns it off."},"refundable":{"type":"boolean","default":false},"sortOrder":{"type":"number","default":100},"status":{"type":"string","example":"active"}}}}}}},"400":{"description":"Send { fees: [...] } — the full list of platform fees to keep — The body has no `fees` array.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Send { fees: [...] } — the full list of platform fees to keep","path":"/stowbo/platform-fees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Fees"],"summary":"Replace the platform fee list","description":"**Replaces the platform fee list** with what you send. Each entry is matched to an existing platform fee by `name` (falling back to `code`): a match is updated and set `active`, anything new is created as a `stowbo_fee` record, and every existing platform fee you did not send is set `inactive` — never deleted. An empty array switches them all off.\n\nEntries are normalised on the way in (see the schema defaults) and the stored, normalised list is returned.\n\n#### Signature\n\n```http\nPOST /stowbo/platform-fees (body) -> The stored, normalised fee list\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A body without `fees` is refused rather than read as \"no fees\" — it used to switch every platform fee off.\n- The read cache is cleared on write for the node that handled it; other nodes can serve the old list for up to 30 seconds.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | FEES_REQUIRED | Send { fees: [...] } — the full list of platform fees to keep | The body has no `fees` array. | Send the full list; `[]` switches every fee off. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/platform-fees`","requestBody":{"description":"The complete fee list.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["fees"],"properties":{"fees":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Key of the fee. `POST` matches existing fees by `name`; defaults to `code`, then `fee`.","example":"service"},"code":{"type":"string","description":"Machine code. Defaults to `name`.","example":"service"},"label":{"type":"string","description":"Shown on the bill. Read from `title`, `label` or `name`.","example":"Service fee"},"type":{"type":"string","enum":["platform","tax","processing"],"default":"platform","example":"platform"},"paidBy":{"type":"string","enum":["guest","host"],"default":"guest","description":"Who pays it. Anything other than `host` is `guest`."},"trigger":{"type":"string","default":"booking","description":"When it applies.","example":"booking"},"appliesTo":{"type":"string","enum":["checkout","item"],"description":"`checkout` once per bill, `item` once per thing booked. Legacy `per: unit` maps to `item`.","example":"checkout"},"basis":{"type":"string","enum":["fixed","percent"],"description":"`fixed` amount, or a `percent` of the goods. Legacy `per: percent` maps to `percent`.","example":"percent"},"amount":{"type":"number","default":0,"example":8},"priceFrom":{"type":"number","description":"Only for bills from this amount."},"priceTo":{"type":"number","description":"Only for bills up to this amount."},"minFee":{"type":"number"},"maxFee":{"type":"number"},"channels":{"type":"array","items":{"type":"string"},"description":"Booking channels it applies to. Empty = all."},"spaceTypes":{"type":"array","items":{"type":"string"},"description":"Space types it applies to. Empty = all."},"validFrom":{"type":"string","format":"date-time"},"validTo":{"type":"string","format":"date-time"},"taxable":{"type":"boolean","default":true,"description":"Only an explicit `false` turns it off."},"refundable":{"type":"boolean","default":false},"sortOrder":{"type":"number","default":100},"status":{"type":"string","example":"active"}}},"description":"Every platform fee to keep."}}},"examples":{"typical":{"summary":"A percentage fee and a per-item fee","value":{"fees":[{"name":"service","title":"Service fee","basis":"percent","amount":8},{"name":"handling","title":"Handling","appliesTo":"item","amount":2.5}]}},"clear":{"summary":"Switch every platform fee off","value":{"fees":[]}}}}}}}},"/stowbo/platform-config":{"get":{"operationId":"StowboController_getPlatformConfig","summary":"Get the platform money config","description":"The tunable platform numbers, read live from the org's `stowbo` setting (no restart): `minPayout` — the payout floor a host cannot go below (a host's own higher minimum wins); `takeRate` — the platform's share of host-earnable money, as a fraction; `gateway`; `holdTtlMinutes` — how long a checkout hold keeps capacity; `appUrl` — the customer app URL; `site` — the site the marketplace runs on. `source` says whether the values came from the setting or the deployment defaults, and `defaults` carries those defaults.\n\n#### Signature\n\n```http\nGET /stowbo/platform-config () -> The effective config and the deployment defaults\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Cached for 30 seconds per node.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/platform-config`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The effective config and the deployment defaults","content":{"application/json":{"schema":{"type":"object","properties":{"minPayout":{"type":"number","example":25},"takeRate":{"type":"number","description":"Fraction 0–1 (0.18 = 18%). The setting stores a percent; this is converted.","example":0.18},"gateway":{"type":"string","enum":["stripe","paypal","authorize"],"example":"stripe"},"holdTtlMinutes":{"type":"number","example":15},"appUrl":{"type":"string","example":"https://stowbo.example.com"},"site":{"type":"string","example":"stowbo"},"source":{"type":"string","enum":["setting","default"]},"defaults":{"type":"object","properties":{"holdTtlMinutes":{"type":"number"},"appUrl":{"type":"string"},"takeRate":{"type":"number"},"gateway":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Setup"]},"post":{"operationId":"StowboController_setPlatformConfig","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The effective config after the change","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Gateway must be one of stripe, paypal, authorize — `gateway` is not a supported gateway.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway must be one of stripe, paypal, authorize","path":"/stowbo/platform-config","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Setup"],"summary":"Change the platform money config","description":"Merges what you send into the platform config and returns the result. Only the fields sent change.\n\n#### Signature\n\n```http\nPOST /stowbo/platform-config (body) -> The effective config after the change\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_GATEWAY | Gateway must be one of stripe, paypal, authorize | `gateway` is not a supported gateway. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/platform-config`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"minPayout":{"type":"number","description":"Cannot be negative.","example":25},"takeRate":{"type":"number","description":"A fraction between 0 and 1 (0.18 = 18%), **not a percent**.","example":0.18},"gateway":{"type":"string","enum":["stripe","paypal","authorize"]},"holdTtlMinutes":{"type":"number","description":"1–1440.","example":15},"appUrl":{"type":"string","description":"Must start with http:// or https://.","example":"https://stowbo.example.com"},"site":{"type":"string","description":"Name of an existing site in the org.","example":"stowbo"}}},"example":{"takeRate":0.18,"minPayout":25}}}}}},"/stowbo/handovers":{"get":{"operationId":"StowboController_handovers","summary":"List delegated pickups","description":"Every hand-off across all hosts — who was to collect what, until when, what had to be verified, and what happened — plus the refusals. For disputes.\n\n#### Signature\n\n```http\nGET /stowbo/handovers (from?: string, to?: string, listing?: string, host?: string, customer?: string, booking?: string, status?: string, search?: string, page?: integer, pageSize?: integer, sort?: string, sortType?: string) -> A page of hand-offs, the refusals, and totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/handovers/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer","in":"query","required":false,"schema":{"type":"string"}},{"name":"booking","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["active","completed","revoked","expired"]}},{"name":"search","in":"query","required":false,"description":"Collector, phone, email, customer, booking, listing, host, code, note or item label.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","in":"query","required":false,"description":"At most 500.","schema":{"type":"integer","default":50},"example":50},{"name":"sort","in":"query","required":false,"schema":{"type":"string"}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"A page of hand-offs, the refusals, and totals","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Hand-offs"},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"},"refusals":{"type":"array","items":{"type":"object","additionalProperties":true}},"totals":{"type":"object","properties":{"handoffs":{"type":"number"},"active":{"type":"number"},"completed":{"type":"number"},"revoked":{"type":"number"},"expired":{"type":"number"},"refusals":{"type":"number"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Pickups"]}},"/stowbo/handovers/{id}":{"get":{"operationId":"StowboController_handoverDetail","summary":"Get one hand-off","description":"The hand-off in full: the collector, the items, the checks demanded, refusals, the booking timeline entries about it, and other hand-offs on the same booking.\n\n#### Signature\n\n```http\nGET /stowbo/handovers/{id} (id: string) -> { handoff, refusals, events, others }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | HANDOFF_NOT_FOUND | Hand-off not found | No hand-off has that id. | List them with `GET /stowbo/handovers`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Hand-off (`stowbo_pickup_delegation`) sk.","example":"66f1a2b3c4d5e6f708192a3d"}],"responses":{"200":{"description":"{ handoff, refusals, events, others }","content":{"application/json":{"schema":{"type":"object","properties":{"handoff":{"type":"object","additionalProperties":true},"refusals":{"type":"array","items":{"type":"object","additionalProperties":true}},"events":{"type":"array","items":{"type":"object","additionalProperties":true}},"others":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Hand-off not found — No hand-off has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Hand-off not found","path":"/stowbo/handovers/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Pickups"]}},"/stowbo/handovers/{id}/resend":{"post":{"operationId":"StowboController_resendHandover","summary":"Re-send a pickup pass","description":"Issues a new pass link and sends it to the collector. The old link stops working.\n\n#### Signature\n\n```http\nPOST /stowbo/handovers/{id}/resend (id: string) -> The hand-off with its new `passUrl`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | HANDOFF_NOT_FOUND | Hand-off not found | No hand-off has that id. | List them with `GET /stowbo/handovers`. |\n| `409` | HANDOFF_NOT_ACTIVE | This hand-off is no longer active | It was completed, revoked or has expired. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Hand-off (`stowbo_pickup_delegation`) sk.","example":"66f1a2b3c4d5e6f708192a3d"}],"responses":{"201":{"description":"The hand-off with its new `passUrl`","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"photo":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"The pass link. Returned on create and resend only."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Hand-off not found — No hand-off has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Hand-off not found","path":"/stowbo/handovers/{id}/resend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This hand-off is no longer active — It was completed, revoked or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This hand-off is no longer active","path":"/stowbo/handovers/{id}/resend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Pickups"]}},"/stowbo/handovers/{id}/revoke":{"post":{"operationId":"StowboController_revokeHandover","summary":"Revoke a hand-off","description":"Cancels the hand-off: the items go back to the owner's own pickup code and the collector is told. Revoking an already-revoked hand-off returns it unchanged.\n\n#### Signature\n\n```http\nPOST /stowbo/handovers/{id}/revoke (id: string) -> The hand-off\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | HANDOFF_NOT_FOUND | Hand-off not found | No hand-off has that id. | List them with `GET /stowbo/handovers`. |\n| `409` | ALREADY_COLLECTED | Already collected | The items were already handed over. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Hand-off (`stowbo_pickup_delegation`) sk.","example":"66f1a2b3c4d5e6f708192a3d"}],"responses":{"201":{"description":"The hand-off","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"photo":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"The pass link. Returned on create and resend only."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Hand-off not found — No hand-off has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Hand-off not found","path":"/stowbo/handovers/{id}/revoke","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already collected — The items were already handed over.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already collected","path":"/stowbo/handovers/{id}/revoke","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Pickups"]}},"/stowbo/booking/{bookingId}/handovers":{"post":{"operationId":"StowboController_createHandover","summary":"Hand a pickup to someone else","description":"On the customer's behalf: creates a hand-off with its own pickup code and pass link, sends the pass to the collector and a notice to the customer.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/handovers (bookingId: string, body) -> The hand-off, with `passUrl`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | ITEMS_REQUIRED | Pick at least one item to hand off | `items` is empty. | — |\n| `409` | BOOKING_NOT_ACTIVE | This booking is no longer active | Cancelled, no-show, settled or completed. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The hand-off, with `passUrl`","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"photo":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"The pass link. Returned on create and resend only."}}}}}},"400":{"description":"Pick at least one item to hand off — `items` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick at least one item to hand off","path":"/stowbo/booking/{bookingId}/handovers","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/handovers","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This booking is no longer active — Cancelled, no-show, settled or completed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This booking is no longer active","path":"/stowbo/booking/{bookingId}/handovers","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Pickups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"string"},"description":"Booking-item ids to hand off. Each must be on the booking, still in custody, and not on another live hand-off."},"name":{"type":"string","description":"The collector's name.","example":"Sam Doe"},"phone":{"type":"string","description":"Collector phone. A phone or an email is required.","example":"+15125550100"},"email":{"type":"string","description":"Collector email.","example":"sam@example.com"},"verify":{"type":"string","enum":["qr","qr_name","qr_id"],"default":"qr_id","description":"What the host must check at pickup: the QR only, QR + name, or QR + photo ID."},"validUntil":{"type":"string","format":"date-time","description":"Defaults to, and is capped at, the latest end date of the items."},"note":{"type":"string"},"photo":{"type":"string","description":"Photo of the collector."}}},"example":{"items":["66f1a2b3c4d5e6f708192a3c"],"name":"Sam Doe","phone":"+15125550100","verify":"qr_id"}}}}}},"/stowbo/site":{"get":{"operationId":"StowboController_getStowboSite","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The site record, or null","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"nullable":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Setup"],"summary":"Get the Stowbo site","description":"The site record the marketplace runs on — the one named by the platform config's `site` (setup creates it as `stowbo`). `null` when that site does not exist.\n\n#### Signature\n\n```http\nGET /stowbo/site () -> The site record, or null\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/setup/status`"}},"/stowbo/booking/{bookingId}/extend":{"post":{"operationId":"StowboController_extendStay","summary":"Extend a stay","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The extended line, what was added, and the booking's new total and balance","content":{"application/json":{"schema":{"type":"object","properties":{"line":{"type":"number","example":0},"endDate":{"type":"string","format":"date-time","example":"2026-09-06T17:00:00.000Z"},"added":{"type":"number","description":"Price difference. Zero or negative when the re-price came out cheaper.","example":30},"paid":{"type":"boolean","description":"Whether a payment was taken for the extra time.","example":true},"total":{"type":"number","example":75},"balance":{"type":"number","example":0}}},"example":{"line":0,"endDate":"2026-09-06T17:00:00.000Z","added":30,"paid":true,"total":75,"balance":0}}}},"400":{"description":"endDate is required — `endDate` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"endDate is required","path":"/stowbo/booking/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"description":"Extending adds <amount> <currency> — payment required. — The extra time costs more and no payment method or intent was sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"Extending adds <amount> <currency> — payment required.","path":"/stowbo/booking/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Cannot extend — the space is not free for that time (N of M available). — The space is booked out, blacked out, or blocked by a parent listing for part of the extra window.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"code":"UNAVAILABLE","message":"Cannot extend — the space is not free for that time (0 of 1 available).","available":0,"until":"blackout"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"description":"Extends a space line to a later end date and re-prices it.\n\nThe order of operations is deliberate and matters:\n\n1. The new window is **priced but not committed**.\n2. The space must be **free for the extra time only** — the interval from the current end to the new end. The booking's own line still ends at the old date, so it is not counted against the window it is growing into. A booking that starts right after this one blocks the extension.\n3. The price difference is **collected first**. A declined payment changes nothing — the stay keeps its original window.\n\nA re-price that comes out cheaper or equal (hitting a daily cap, for instance) skips the payment step.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/extend (bookingId: string, body) -> The extended line, what was added, and the booking's new total and balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The 409 body is structured, not the standard error envelope: it carries `code`, `message`, `available` and, when a closure caused it, `until: \"blackout\"`.\n- Nothing is written until both the availability check and the payment succeed, so a failed extension leaves the booking untouched.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | END_DATE_REQUIRED | endDate is required | `endDate` is missing. | Send the new end of the stay. |\n| `409` | UNAVAILABLE | Cannot extend — the space is not free for that time (N of M available). | The space is booked out, blacked out, or blocked by a parent listing for part of the extra window. | Check `GET /stowbo/listing/{listing}/calendar` for the next free window, or move the guest with `move-unit`. |\n| `402` | PAYMENT_REQUIRED | Extending adds <amount> <currency> — payment required. | The extra time costs more and no payment method or intent was sent. | Retry with `paymentMethodId` or a confirmed `paymentIntentId`. The body carries `dueNow` and `currency`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/listing/{listing}/calendar`\n- `POST /stowbo/booking/{bookingId}/move-unit`","requestBody":{"description":"The new end date, and which line to extend.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["endDate"],"properties":{"endDate":{"type":"string","format":"date-time","description":"New end of the stay. Must be after the line's `startDate`.","example":"2026-09-06T17:00:00.000Z"},"line":{"type":"number","default":0,"description":"Zero-based index of the line to extend. Defaults to the first line.","example":0},"paymentMethodId":{"type":"string","description":"Card to charge for the extra time (web).","example":"pm_1Abc123"},"paymentIntentId":{"type":"string","description":"A confirmed payment intent for the extra time (native, Apple/Google Pay).","example":"pi_3Abc123"}}},"examples":{"simple":{"summary":"Extend the first line by two days","value":{"endDate":"2026-09-06T17:00:00.000Z","paymentMethodId":"pm_1Abc123"}},"specificLine":{"summary":"Extend a specific line","value":{"line":1,"endDate":"2026-09-06T17:00:00.000Z"}}}}}}}},"/stowbo/booking/{bookingId}/move-unit":{"post":{"operationId":"StowboController_moveUnit","summary":"Move stored things to another unit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The line index and its new unit","content":{"application/json":{"schema":{"type":"object","properties":{"line":{"type":"number","example":0},"unit":{"type":"string","example":"L-22"}}},"example":{"line":0,"unit":"L-22"}}}},"400":{"description":"unit is required — `unit` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"unit is required","path":"/stowbo/booking/{bookingId}/move-unit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/move-unit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"description":"Reassigns a booking line to a different unit. The old unit is released back to `vacant` and the new one is marked `occupied` against this booking — leaving the old one held is how a unit silently disappears from availability forever.\n\nThe guest is notified that their spot changed.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/move-unit (bookingId: string, body) -> The line index and its new unit\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The target unit is not checked for being free. Moving into an occupied unit will overwrite its booking reference.\n- Because the release happens first, a failure at the lookup step leaves the booking without a held unit. Retry with a valid unit to restore a consistent state.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | UNIT_REQUIRED | unit is required | `unit` is missing. | Send the target unit. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/custody`","requestBody":{"description":"The unit to move to.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["unit"],"properties":{"unit":{"type":"string","description":"Target unit name or id.","example":"L-22"},"line":{"type":"number","default":0,"description":"Zero-based index of the line to move.","example":0},"reason":{"type":"string","description":"Included in the audit event and the guest notification.","example":"Original locker jammed"}}},"example":{"unit":"L-22","reason":"Original locker jammed"}}}}}},"/stowbo/booking/{bookingId}/access":{"post":{"operationId":"StowboController_logAccess","summary":"Record an access","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The booking, with the event appended","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/access","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"description":"Appends an access event to the booking timeline — who opened the unit, and when. This is the record that answers \"was anyone in there\", so it is written on every open regardless of who did it.\n\nIt records only; it does not unlock anything or check whether access was permitted.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/access (bookingId: string, body) -> The booking, with the event appended\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Audit writes never fail the operation they describe — if the event cannot be appended, the call still succeeds.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/booking/{bookingId}/timeline`","requestBody":{"description":"Who accessed it. Everything is optional — the caller is used as the actor by default.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"actor":{"type":"string","description":"Who opened it. Defaults to the authenticated caller.","example":"ada@example.com"},"actorRole":{"type":"string","enum":["guest","operator","host"],"default":"guest","description":"In what capacity.","example":"guest"},"line":{"type":"number","description":"Which line was accessed.","example":0},"detail":{"type":"string","default":"Unit accessed","description":"Free-text note.","example":"Collected two boxes"}}},"examples":{"guest":{"summary":"Guest opened their unit","value":{"actorRole":"guest","line":0,"detail":"Collected two boxes"}},"staff":{"summary":"Staff access","value":{"actor":"staff@venue.com","actorRole":"operator","detail":"Routine inspection"}}}}}}}},"/stowbo/booking/{bookingId}/timeline":{"get":{"operationId":"StowboController_timeline","summary":"Get everything that happened to a booking","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"200":{"description":"The booking history","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"type":"string","description":"Booking `sk`.","example":"66f1a2b3c4d5e6f708192a3b"},"status":{"type":"string","example":"stored"},"timeline":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"What happened — `access`, `unit_changed`, `adjustment`, `cancelled`, `note`, …","example":"access"},"at":{"type":"string","format":"date-time","example":"2026-09-02T11:14:00.000Z"},"actor":{"type":"string","description":"Who did it.","example":"ada@example.com"},"actorRole":{"type":"string","enum":["guest","operator","host"],"example":"guest"},"line":{"type":"number","description":"Line the event concerns, when it concerns one.","example":0},"amount":{"type":"number","description":"Present on money events."},"detail":{"type":"string","example":"Unit accessed"}}},"description":"Events sorted oldest first."},"adjustments":{"type":"array","description":"The money ledger. Charges add, discounts and refunds subtract; a reversal is kept as its own opposite line rather than removing the original, so the mistake and the correction are both visible.","items":{"type":"object","properties":{"kind":{"type":"string","description":"`charge` adds; anything else subtracts.","example":"charge"},"code":{"type":"string","example":"damage"},"label":{"type":"string","example":"Additional charge"},"amount":{"type":"number","example":25},"bearer":{"type":"string","enum":["guest","host"],"example":"guest"}}}},"claims":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Damage or dispute claims raised against the booking."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"description":"The full history of a booking in chronological order, together with its money adjustments and any claims. This is the single read for an audit view — timeline, ledger and disputes in one response.\n\n#### Signature\n\n```http\nGET /stowbo/booking/{bookingId}/timeline (bookingId: string) -> The booking history\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/access`\n- `POST /stowbo/booking/{bookingId}/charge`"}},"/stowbo/booking/{bookingId}/hold":{"post":{"operationId":"StowboController_holdPayment","summary":"Hold the money on a booking","description":"Puts a booking's money on hold. Two separate things stop: the guest's card stays authorised but is not captured, and — the part that matters — **the host is not paid at settle**.\n\nOnce money reaches a host wallet and is withdrawn, a damage claim has nothing to claw back from, so a contested stay must be held *before* it settles.\n\nA hold does not forgive anything. The guest can still be charged and what is owed is still owed.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/hold (bookingId: string, body) -> The hold that was recorded on the booking\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Holding an already-held booking overwrites the previous hold, including who placed it and when.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/release-hold`\n- `POST /stowbo/booking/{bookingId}/charge`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The hold that was recorded on the booking","content":{"application/json":{"schema":{"type":"object","properties":{"hold":{"type":"object","properties":{"reason":{"type":"string"},"note":{"type":"string"},"heldAt":{"type":"string","format":"date-time"},"heldBy":{"type":"string"}}}}},"example":{"hold":{"reason":"damage_claim","note":"Guest reports water damage to stored items","heldAt":"2026-09-04T10:00:00.000Z","heldBy":"ops@venue.com"}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"description":"Why the money is being held.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","default":"under_review","description":"Machine-readable reason.","example":"damage_claim"},"note":{"type":"string","description":"Free-text detail for whoever reviews it.","example":"Guest reports water damage to stored items"}}},"example":{"reason":"damage_claim","note":"Guest reports water damage to stored items"}}}}}},"/stowbo/booking/{bookingId}/release-hold":{"post":{"operationId":"StowboController_releaseHold","summary":"Release a payment hold","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"Confirmation the hold was cleared","content":{"application/json":{"schema":{"type":"object","properties":{"released":{"type":"boolean","example":true}}},"example":{"released":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/release-hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"description":"Clears the hold so the booking settles and pays out normally. The release is recorded on the timeline with any note you pass.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/release-hold (bookingId: string, body) -> Confirmation the hold was cleared\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Releasing a booking that has no hold succeeds and returns `{ released: true }` — it is not an error.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/hold`","requestBody":{"description":"Optional note explaining the release.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","description":"Recorded on the timeline.","example":"Claim withdrawn"}}},"example":{"note":"Claim withdrawn"}}}}}},"/stowbo/booking/{bookingId}/charge":{"post":{"operationId":"StowboController_chargeGuest","summary":"Charge the guest now","description":"Adds a charge and attempts to take it immediately, without waiting for settle.\n\n**The ledger is written first, the capture second.** If the capture fails, the charge still stands as owed — that is the honest state, and silently dropping a failed capture is how a platform ends up absorbing damage costs it had already recorded. The response tells you which happened via `captured` and `captureError`, and a `200` does **not** mean the money was taken.\n\nThe guest is notified either way, with wording that reflects whether it was charged or added to their balance.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/charge (bookingId: string, body) -> The ledger entry, the new total, and whether the capture succeeded\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Always check `captured`. A successful response with `captured: false` means the guest owes the money but has not paid it.\n- Not idempotent — each call adds another ledger line.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | INVALID_AMOUNT | amount must be positive | `amount` is missing, zero or negative. | Send a positive amount. Use a refund to move money the other way. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/booking/{bookingId}/timeline`\n- `POST /stowbo/booking/{bookingId}/hold`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The ledger entry, the new total, and whether the capture succeeded","content":{"application/json":{"schema":{"type":"object","properties":{"entry":{"type":"object","additionalProperties":true,"description":"The adjustment written to the ledger."},"total":{"type":"number","description":"Booking total after the adjustment.","example":70},"captured":{"type":"boolean","description":"Whether the money was actually taken. **False means the charge is recorded but unpaid.**","example":true},"captureError":{"type":"string","nullable":true,"description":"Why the capture failed, when it did.","example":null}}},"example":{"entry":{"kind":"adjustment","code":"damage","amount":25,"bearer":"guest"},"total":70,"captured":false,"captureError":"card_declined"}}}},"400":{"description":"amount must be positive — `amount` is missing, zero or negative.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"amount must be positive","path":"/stowbo/booking/{bookingId}/charge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/charge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"description":"What to charge, and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Amount to charge. Must be greater than zero.","example":25},"category":{"type":"string","default":"other","description":"Charge category. `code` is accepted as an alias.","example":"damage"},"code":{"type":"string","description":"Alias for `category`."},"description":{"type":"string","default":"Additional charge","description":"Shown to the guest. `label` is accepted as an alias.","example":"Replacement lock"},"label":{"type":"string","description":"Alias for `description`."},"hostPortion":{"type":"number","description":"Share of the charge attributed to the host."},"files":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Evidence attached to the charge — photos, reports."},"line":{"type":"number","description":"Line the charge relates to.","example":0}}},"examples":{"damage":{"summary":"Charge for damage, with evidence","value":{"amount":25,"category":"damage","description":"Replacement lock","line":0}},"simple":{"summary":"Ad-hoc charge","value":{"amount":10,"description":"Extra handling"}}}}}}}},"/stowbo/booking/{bookingId}/cancel":{"post":{"operationId":"StowboController_cancelBooking","summary":"Cancel a booking","description":"Cancels a booking and unwinds it in a fixed order: every held unit is released back to `vacant`, the guest's uncaptured authorisation is voided, the refund is issued, and the deposit — which is a hold, not a charge — comes back in full, because nothing was stored to damage.\n\nThe refund amount follows the cancellation policy unless you override it. Inside the free-cancellation window the guest gets everything back; outside it, the fraction the policy promised. Pass `refundAmount` to override both.\n\nAny cancellation or no-show fee the policy charges is added to the bill, and the bill is released down to what is kept plus what is still due.\n\nThe record is kept. Cancellations are evidence, so the booking is marked `cancelled` (or `no_show`) rather than deleted, and the refund goes back through the gateway rather than by rewriting the total.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/cancel (bookingId: string, body) -> The cancelled booking record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `refundAmount` overrides the policy completely, including the free-cancellation window. Send `0` to cancel with no refund.\n- The refund runs against the charges actually recorded for the booking, so a guest who never paid receives nothing regardless of the policy.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | ALREADY_CLOSED | Cannot cancel a <status> booking | The booking is already `settled` or `cancelled`. | A settled booking is refunded rather than cancelled — use `POST /stowbo/booking/{bookingId}/refund` or `POST /stowbo/transactions/{ref}/refund`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/refund`\n- `GET /stowbo/booking/{bookingId}/timeline`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The cancelled booking record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot cancel a <status> booking — The booking is already `settled` or `cancelled`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot cancel a <status> booking","path":"/stowbo/booking/{bookingId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"description":"Cancellation details. All optional.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Recorded on the timeline and sent to the guest.","example":"Guest no longer needs the space"},"noShow":{"type":"boolean","default":false,"description":"Record a no-show: the booking ends `no_show` instead of `cancelled`, and the policy's no-show terms (not its cancellation terms) decide fees and refund.","example":false},"refundAmount":{"type":"number","description":"Operator override. Bypasses the cancellation policy entirely — send `0` to refund nothing.","example":0},"actorRole":{"type":"string","enum":["guest","operator","host"],"default":"operator","description":"Who cancelled it."}}},"examples":{"policy":{"summary":"Cancel under the policy","value":{"reason":"Guest no longer needs the space"}},"noShow":{"summary":"Record a no-show","value":{"noShow":true,"reason":"Never arrived"}},"override":{"summary":"Full refund as a goodwill override","value":{"refundAmount":45,"reason":"Our error"}}}}}}}},"/stowbo/listing/{listing}/calendar":{"get":{"operationId":"StowboController_calendar","summary":"Get the occupancy calendar for a space","description":"A day-by-day occupancy grid for a listing, computed on the server so the operator console, the host app and the booking screen cannot disagree about what is free.\n\nA site holds no bookings of its own — its child listings do — so asking a site sums its children's capacity and reads their bookings. Asking a leaf listing reports that listing alone.\n\nIt answers per unit (`units[].days[]`: `free`, `blocked`, or the state of the booking on that unit) and per day (`capacityByDay`), plus today's numbers, the next free day and the next arrival. An open (metered) stay occupies every day from its start.\n\n#### Signature\n\n```http\nGET /stowbo/listing/{listing}/calendar (listing: string, from?: string, days?: integer) -> The occupancy grid\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `days` is clamped to 120. Asking for a year returns 120 days without warning.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/listing/{listing}/blackouts`\n- `GET /stowbo/calendar`\n- `GET /stowbo/overdue`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"path","schema":{"type":"string"},"description":"Listing `name`. A site aggregates its child listings; a leaf listing stands alone.","example":"downtown-lockers"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"First day of the window. Snapped to the start of that day. Defaults to today.","example":"2026-09-01"},{"name":"days","required":false,"in":"query","schema":{"type":"integer","default":28},"description":"How many days to return. Clamped to 1–120 without an error.","example":28}],"responses":{"200":{"description":"The occupancy grid","content":{"application/json":{"schema":{"type":"object","properties":{"listing":{"type":"string","example":"downtown-lockers"},"title":{"type":"string"},"capacity":{"type":"number","description":"Total capacity. Summed across children for a site.","example":40},"unitBacked":{"type":"boolean","description":"Whether the space has `stowbo_unit` records."},"from":{"type":"string","format":"date-time"},"days":{"type":"number","description":"Days in the window.","example":28},"today":{"type":"object","properties":{"inUse":{"type":"number"},"free":{"type":"number"},"blocked":{"type":"boolean"}}},"nextFree":{"type":"string","format":"date-time","nullable":true},"nextArrival":{"type":"object","nullable":true,"properties":{"date":{"type":"string"},"guest":{"type":"string"},"booking":{"type":"string"}}},"units":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"unitNumber":{"type":"string"},"status":{"type":"string"},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"state":{"type":"string","description":"`free`, `blocked`, or the booking line's state."},"booking":{"type":"string"},"guest":{"type":"string"},"reason":{"type":"string"}}}}}}},"capacityByDay":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"used":{"type":"number"},"capacity":{"type":"number"},"free":{"type":"number","description":"0 on a blocked day."},"blocked":{"type":"boolean"},"reason":{"type":"string"}}}},"blackouts":{"type":"array","items":{"type":"object","additionalProperties":true}},"upcoming":{"type":"array","description":"Booking lines in the window, earliest first.","items":{"type":"object","properties":{"booking":{"type":"string"},"guest":{"type":"string"},"listing":{"type":"string"},"unit":{"type":"string"},"quantity":{"type":"number"},"startDate":{"type":"string"},"endDate":{"type":"string","nullable":true},"state":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Listing <name> not found — No listing in the org has that name or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing <name> not found","path":"/stowbo/listing/{listing}/calendar","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Calendar"]}},"/stowbo/listing/{listing}/blackouts":{"get":{"operationId":"StowboController_listBlackouts","summary":"List the days a space is closed","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"path","schema":{"type":"string"},"description":"Listing `name`. A site aggregates its child listings; a leaf listing stands alone.","example":"downtown-lockers"}],"responses":{"200":{"description":"The listing's closures","content":{"application/json":{"schema":{"type":"object","properties":{"listing":{"type":"string","example":"downtown-lockers"},"blackouts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"blk_1756476202118"},"from":{"type":"string","format":"date-time","example":"2026-10-01T00:00:00.000Z"},"to":{"type":"string","format":"date-time","example":"2026-10-03T00:00:00.000Z"},"reason":{"type":"string","default":"servicing","example":"servicing"},"note":{"type":"string"},"units":{"type":"array","items":{"type":"string"},"description":"Units affected. Empty means the whole listing."},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string","example":"ops@venue.com"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Listing <name> not found — No listing in the org has that name or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing <name> not found","path":"/stowbo/listing/{listing}/blackouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Calendar"],"description":"Returns the closure windows recorded on a listing. Blackouts live on the listing record itself, so they are returned as stored, in insertion order.\n\n#### Signature\n\n```http\nGET /stowbo/listing/{listing}/blackouts (listing: string) -> The listing's closures\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/listing/{listing}/blackouts`"},"post":{"operationId":"StowboController_addBlackout","summary":"Close a space for servicing","description":"Adds a closure window to a listing.\n\n**Bookings already inside the window are not cancelled.** They come back in `conflicts` for the operator to resolve — a system that quietly voids paid bookings to make a maintenance window fit is worse than one that reports the clash. The blackout is still written; it is your job to move or cancel the guests it names.\n\nConflicts are checked across the listing **and its descendants**, so closing a site surfaces bookings held by its child listings.\n\n#### Signature\n\n```http\nPOST /stowbo/listing/{listing}/blackouts (listing: string, body) -> The closure, the full list, and any bookings caught inside it\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A non-empty `conflicts` array is not an error and does not prevent the closure — always inspect it after closing a space.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |\n| `400` | WINDOW_REQUIRED | from and to are required | Either bound is missing. | Send both ends of the closure. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /stowbo/listing/{listing}/blackouts/{blackoutId}`\n- `GET /stowbo/listing/{listing}/calendar`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"path","schema":{"type":"string"},"description":"Listing `name`. A site aggregates its child listings; a leaf listing stands alone.","example":"downtown-lockers"}],"responses":{"201":{"description":"The closure, the full list, and any bookings caught inside it","content":{"application/json":{"schema":{"type":"object","properties":{"blackout":{"type":"object","additionalProperties":true,"description":"The closure just added."},"blackouts":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Every closure on the listing after the write."},"conflicts":{"type":"array","description":"**Bookings already inside the window.** These were not cancelled — resolve them yourself.","items":{"type":"object","properties":{"booking":{"type":"string","example":"STW-4821"},"listing":{"type":"string","example":"downtown-lockers"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"state":{"type":"string","example":"stored"}}}}}}}}},"400":{"description":"from and to are required — Either bound is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"from and to are required","path":"/stowbo/listing/{listing}/blackouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Listing <name> not found — No listing in the org has that name or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing <name> not found","path":"/stowbo/listing/{listing}/blackouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Calendar"],"requestBody":{"description":"The window to close.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date-time","description":"Start of the closure.","example":"2026-10-01T00:00:00.000Z"},"to":{"type":"string","format":"date-time","description":"End of the closure. Must be after `from`.","example":"2026-10-03T00:00:00.000Z"},"reason":{"type":"string","default":"servicing","example":"servicing"},"note":{"type":"string","example":"Annual lock replacement"},"units":{"type":"array","items":{"type":"string"},"description":"Restrict the closure to specific units. Omit to close the whole listing.","example":["L-14","L-15"]},"capacity":{"type":"number","description":"Cap the space at this many for the window instead of closing it. `0` closes. See `POST /stowbo/listing/{listing}/capacity`.","example":5}}},"examples":{"wholeListing":{"summary":"Close the whole listing","value":{"from":"2026-10-01T00:00:00.000Z","to":"2026-10-03T00:00:00.000Z","reason":"servicing","note":"Annual lock replacement"}},"someUnits":{"summary":"Close two units only","value":{"from":"2026-10-01T00:00:00.000Z","to":"2026-10-03T00:00:00.000Z","units":["L-14","L-15"]}}}}}}}},"/stowbo/listing/{listing}/blackouts/{blackoutId}":{"delete":{"operationId":"StowboController_removeBlackout","summary":"Reopen a space","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"path","schema":{"type":"string"},"description":"Listing `name`. A site aggregates its child listings; a leaf listing stands alone.","example":"downtown-lockers"},{"name":"blackoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Closure id, from the blackout list.","example":"blk_1756476202118"}],"responses":{"200":{"description":"The remaining closures","content":{"application/json":{"schema":{"type":"object","properties":{"blackouts":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Listing <name> not found — No listing in the org has that name or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing <name> not found","path":"/stowbo/listing/{listing}/blackouts/{blackoutId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Calendar"],"description":"Removes a closure from a listing, making those days bookable again. Returns the remaining closures.\n\n#### Signature\n\n```http\nDELETE /stowbo/listing/{listing}/blackouts/{blackoutId} (listing: string, blackoutId: string) -> The remaining closures\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Removing an id that does not exist succeeds and returns the unchanged list — it is not a `404`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/listing/{listing}/blackouts`"}},"/stowbo/custody":{"get":{"operationId":"StowboController_custody","summary":"List what is in custody right now","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one listing (exact name; child listings are not included).","example":"downtown-lockers"}],"responses":{"200":{"description":"Items in custody","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string","description":"`stowbo_booking_item` sk."},"booking":{"type":"string"},"customer":{"type":"string"},"listing":{"type":"string"},"label":{"type":"string","nullable":true},"unit":{"type":"string","nullable":true},"identifier":{"type":"string","nullable":true,"description":"Plate, bag tag, …"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time","nullable":true,"description":"Null for an open (metered) stay."},"checkedInAt":{"type":"string","format":"date-time","nullable":true},"checkedOutAt":{"type":"string","format":"date-time","nullable":true},"status":{"type":"string"},"present":{"type":"boolean","description":"Physically here now (last movement was in)."},"request":{"type":"object","nullable":true,"description":"An open guest request the host must act on."},"overdueSince":{"type":"string","format":"date-time","description":"Only on overdue entries."},"overdueMs":{"type":"number","description":"Only on overdue entries."}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"Every booking **item** in an open custody session (checked in, not checked out) — one entry per thing, not per booking. Overdue items come first, then those due to leave today, then the rest, each group ordered by end date. Cancelled and ended items are excluded.\n\n#### Signature\n\n```http\nGET /stowbo/custody (listing?: string) -> Items in custody\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Reads at most 5,000 booking items.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/overdue`\n- `GET /stowbo/items`"}},"/stowbo/overdue":{"get":{"operationId":"StowboController_overdue","summary":"List what is past its window and still here","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Overdue items, longest overdue first","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string","description":"`stowbo_booking_item` sk."},"booking":{"type":"string"},"customer":{"type":"string"},"listing":{"type":"string"},"label":{"type":"string","nullable":true},"unit":{"type":"string","nullable":true},"identifier":{"type":"string","nullable":true,"description":"Plate, bag tag, …"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time","nullable":true,"description":"Null for an open (metered) stay."},"checkedInAt":{"type":"string","format":"date-time","nullable":true},"checkedOutAt":{"type":"string","format":"date-time","nullable":true},"status":{"type":"string"},"present":{"type":"boolean","description":"Physically here now (last movement was in)."},"request":{"type":"object","nullable":true,"description":"An open guest request the host must act on."},"overdueSince":{"type":"string","format":"date-time","description":"The end date that has passed."},"overdueMs":{"type":"number","description":"Milliseconds past the end date, at the moment of the request.","example":259200000}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"Every booking item in custody whose end date has passed — the work queue for chasing guests and the basis for overstay charges. Derived, never stored: an item leaves this list the moment it is checked out or its stay is extended. Open (metered) stays have no end and never appear here.\n\n#### Signature\n\n```http\nGET /stowbo/overdue () -> Overdue items, longest overdue first\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Takes no `listing` filter — it always spans the whole org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/custody`\n- `POST /stowbo/booking/{bookingId}/extend`"}},"/stowbo/setup":{"post":{"operationId":"StowboController_runSetup","summary":"Set Stowbo up","description":"Seeds the `stowbo-host` CRM benefit and the custody workflow pipeline. Idempotent — safe to run again. Until it has run nobody can apply to host: the benefit their application enrols into does not exist.\n\n#### Signature\n\n```http\nPOST /stowbo/setup () -> What was created or already present\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/setup/status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was created or already present","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Setup"]}},"/stowbo/setup/status":{"get":{"operationId":"StowboController_setupStatus","summary":"Check what is set up","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Setup state","content":{"application/json":{"schema":{"type":"object","properties":{"orgId":{"type":"string"},"ready":{"type":"boolean"},"hostBenefit":{"type":"boolean"},"custodyPipeline":{"type":"boolean"},"site":{"type":"object","additionalProperties":true,"nullable":true}}},"example":{"orgId":"acme","ready":false,"hostBenefit":true,"custodyPipeline":true,"site":null}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Setup"],"description":"Whether the host benefit, the custody pipeline and the Stowbo site exist. `ready` is true only when all three do.\n\n#### Signature\n\n```http\nGET /stowbo/setup/status () -> Setup state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/setup`"}},"/stowbo/availability":{"get":{"operationId":"StowboController_availability","summary":"Check how many of a space are free","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"query","schema":{"type":"string"},"description":"Listing name.","example":"downtown-lockers"},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"},"example":"2026-09-05T09:00:00.000Z"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"Omit for an open (metered) stay.","example":"2026-09-08T17:00:00.000Z"},{"name":"quantity","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1}],"responses":{"200":{"description":"Availability for the window (`available`, and `blackout` / `blockedByTree` when a closure is the reason)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Listing <name> not found — No listing in the org has that name or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing <name> not found","path":"/stowbo/availability","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Calendar"],"description":"How many of a listing are free for a window, after existing bookings, blackouts and closures up the listing tree. The same check a booking makes.\n\n#### Signature\n\n```http\nGET /stowbo/availability (listing?: string, startDate?: string, endDate?: string, quantity?: integer) -> Availability for the window (`available`, and `blackout` / `blockedByTree` when a closure is the reason)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/listing/{listing}/calendar`"}},"/stowbo/listing/{listing}/addons":{"get":{"operationId":"StowboController_listingAddOns","summary":"List the add-ons a space offers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"path","schema":{"type":"string"},"description":"Listing `name`. A site aggregates its child listings; a leaf listing stands alone.","example":"downtown-lockers"}],"responses":{"200":{"description":"Resolved add-ons","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Listing <name> not found — No listing in the org has that name or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing <name> not found","path":"/stowbo/listing/{listing}/addons","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Calendar"],"description":"The add-ons a listing offers — its own plus every ancestor's, since a site's add-ons apply to the spaces inside it.\n\n#### Signature\n\n```http\nGET /stowbo/listing/{listing}/addons (listing: string) -> Resolved add-ons\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/addons`"}},"/stowbo/booking/{bookingId}/settle":{"post":{"operationId":"StowboController_settle","summary":"Settle a booking","description":"The final charge: adds any overstay, splits host-earnable money at the take rate and credits the host wallet. A held booking (`hold`) settles but does not pay the host. Refused while a balance is still owed. The customer is emailed the closing statement.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/settle (bookingId: string) -> The settlement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `409` | BOOKING_UNPAID | Can't settle — <balance> <currency> still owed. Collect payment first. | The booking has an outstanding balance. The body carries `total`, `paid` and `balance`. | Take payment or send a payment request first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/hold`\n- `POST /stowbo/booking/{bookingId}/payment-request`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The settlement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Can't settle — <balance> <currency> still owed. Collect payment first. — The booking has an outstanding balance. The body carries `total`, `paid` and `balance`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Can't settle — <balance> <currency> still owed. Collect payment first.","path":"/stowbo/booking/{bookingId}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"]}},"/stowbo/booking/{bookingId}/adjustments":{"get":{"operationId":"StowboController_listAdjustments","summary":"Get the bill, line by line","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"200":{"description":"{ adjustments, total }","content":{"application/json":{"schema":{"type":"object","properties":{"adjustments":{"type":"array","items":{"type":"object","additionalProperties":true}},"total":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/adjustments","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"description":"Every adjustment on the booking — charges, credits, discounts, refunds and their reversals — and their sum.\n\n#### Signature\n\n```http\nGET /stowbo/booking/{bookingId}/adjustments (bookingId: string) -> { adjustments, total }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/adjustments`"},"post":{"operationId":"StowboController_addAdjustment","summary":"Put a line on the bill","description":"Adds a line to the booking's bill. `bearer` decides who absorbs it: comping a customer costs the platform, a damage charge pays the host. Attach evidence in `files` — a damage charge with no photo does not survive a chargeback.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/adjustments (bookingId: string, body) -> The entry and the booking's new total and balance (plus `refunded`/`refundOk` when a credit refunded money)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | AMOUNT_REQUIRED | amount is required | `amount` is missing or zero. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /stowbo/booking/{bookingId}/adjustments/{index}/reverse`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The entry and the booking's new total and balance (plus `refunded`/`refundOk` when a credit refunded money)","content":{"application/json":{"schema":{"type":"object","properties":{"entry":{"type":"object","additionalProperties":true},"total":{"type":"number"},"balance":{"type":"number"},"refunded":{"type":"number"},"refundOk":{"type":"boolean"}}}}}},"400":{"description":"amount is required — `amount` is missing or zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"amount is required","path":"/stowbo/booking/{bookingId}/adjustments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/adjustments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Non-zero. Negative for a credit.","example":25},"code":{"type":"string","default":"adjustment","example":"damage"},"label":{"type":"string","example":"Broken lock"},"kind":{"type":"string","default":"adjustment"},"bearer":{"type":"string","enum":["guest","host","platform"],"default":"guest"},"hostPortion":{"type":"number","default":0},"files":{"type":"array","items":{"type":"object","additionalProperties":true}},"line":{"type":"number"}}},"example":{"amount":25,"code":"damage","label":"Broken lock","bearer":"guest"}}}}}},"/stowbo/booking/{bookingId}/adjustments/{index}/reverse":{"put":{"operationId":"StowboController_reverseAdjustment","summary":"Undo a line on the bill","description":"Adds the opposite of a line rather than deleting it — the original and the correction both stay visible.\n\n#### Signature\n\n```http\nPUT /stowbo/booking/{bookingId}/adjustments/{index}/reverse (bookingId: string, index: string, body) -> { reversal, total }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `409` | ALREADY_REVERSED | Already reversed | That line has already been reversed. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"},{"name":"index","required":true,"in":"path","schema":{"type":"string"},"description":"Zero-based index of the line in the bill.","example":"2"}],"responses":{"200":{"description":"{ reversal, total }","content":{"application/json":{"schema":{"type":"object","properties":{"reversal":{"type":"object","additionalProperties":true},"total":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/adjustments/{index}/reverse","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already reversed — That line has already been reversed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already reversed","path":"/stowbo/booking/{bookingId}/adjustments/{index}/reverse","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Charged in error"}}}}}}}},"/stowbo/booking/{bookingId}/addon":{"post":{"operationId":"StowboController_addAddon","summary":"Add an add-on to a running booking","description":"Prices the add-on, charges it, and puts it on the bill. A free add-on skips the gateway but still lands on the order; a service add-on also raises a fulfilment request for the host.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/addon (bookingId: string, body) -> The bill entry and totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | INVALID_AMOUNT | An add-on cannot cost less than nothing | `amount` < 0. | — |\n| `402` | PAYMENT_REQUIRED | This add-on is <amount> <currency> — payment required. | Not free and no payment method/intent. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The bill entry and totals","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An add-on cannot cost less than nothing — `amount` < 0.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An add-on cannot cost less than nothing","path":"/stowbo/booking/{bookingId}/addon","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"description":"This add-on is <amount> <currency> — payment required. — Not free and no payment method/intent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"This add-on is <amount> <currency> — payment required.","path":"/stowbo/booking/{bookingId}/addon","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/addon","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"name":{"type":"string","description":"Add-on name/code."},"label":{"type":"string"},"amount":{"type":"number","description":"0 or more."},"code":{"type":"string"},"paymentMethodId":{"type":"string"},"paymentIntentId":{"type":"string"}}},"example":{"name":"insurance","label":"Insurance","amount":5,"paymentMethodId":"pm_1Abc123"}}}}}},"/stowbo/booking/{bookingId}/refund":{"post":{"operationId":"StowboController_refundBooking","summary":"Refund a booking","description":"With `cancel: true` this is a cancellation (`POST /stowbo/booking/{bookingId}/cancel`): `full` refunds everything paid, `amount` refunds that much, neither follows the policy. Without `cancel`, the money goes back as a credit line on the bill and the stay continues; `amount` is required and cannot exceed what has been paid.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/refund (bookingId: string, body) -> The cancelled booking (with `cancel`), or the credit entry and new totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `409` | NOTHING_PAID | Nothing has been paid on this booking — add a credit adjustment instead of a refund | No payment recorded. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/cancel`\n- `POST /stowbo/transactions/{ref}/refund`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The cancelled booking (with `cancel`), or the credit entry and new totals","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Nothing has been paid on this booking — add a credit adjustment instead of a refund — No payment recorded.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Nothing has been paid on this booking — add a credit adjustment instead of a refund","path":"/stowbo/booking/{bookingId}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","example":10},"full":{"type":"boolean"},"cancel":{"type":"boolean"},"reason":{"type":"string"},"noShow":{"type":"boolean"}}},"examples":{"partial":{"summary":"Partial refund, stay continues","value":{"amount":10,"reason":"Late handover"}},"cancelFull":{"summary":"Cancel with a full refund","value":{"cancel":true,"full":true,"reason":"Our error"}}}}}}}},"/stowbo/ledger":{"get":{"operationId":"StowboController_ledger","summary":"List every Stowbo transaction","description":"All `sf_transaction` rows for Stowbo, paged, each carrying what can still be done to it. Totals cover the whole filtered set: in, out, net, pending, failed and unpaid links.\n\n#### Signature\n\n```http\nGET /stowbo/ledger (host?: string, booking?: string, customer?: string, listing?: string, status?: string, kind?: string, type?: string, search?: string, from?: string, to?: string, page?: integer, pageSize?: integer, sort?: string, sortType?: string) -> A page of transactions and totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/transactions/{ref}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"booking","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer","in":"query","required":false,"schema":{"type":"string"}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"page","in":"query","required":false,"schema":{"type":"integer"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","default":50}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["at","createdAt","amount","status","kind","type","customer","ref"]}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"A page of transactions and totals","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Transactions"},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"},"totals":{"type":"object","properties":{"count":{"type":"number"},"in":{"type":"number"},"out":{"type":"number"},"net":{"type":"number"},"pending":{"type":"number"},"failed":{"type":"number"},"links":{"type":"number"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Ledger"]}},"/stowbo/discounts":{"get":{"operationId":"StowboController_discounts","summary":"List discounts","description":"Every discount that can touch a Stowbo booking — the platform's own and every host coupon — with scope, owner, validity, uses and what bookings actually redeemed. With `from`/`to`, redemptions are also counted for the period.\n\n#### Signature\n\n```http\nGET /stowbo/discounts (host?: string, listing?: string, status?: string, search?: string, from?: string, to?: string, sort?: string, sortType?: string, page?: integer, pageSize?: integer) -> A page of discounts and totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Comma-separated: live, expired, exhausted, scheduled, active, inactive, draft.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"redemptions"}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"A page of discounts and totals","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Discounts"},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"},"totals":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Discounts"]},"post":{"operationId":"StowboController_createDiscount","summary":"Create a platform discount","description":"A discount scoped to Stowbo and owned by nobody, so no host can edit it. `listings` empty = every space.\n\n#### Signature\n\n```http\nPOST /stowbo/discounts (body) -> The discount detail (as `GET /stowbo/discounts/{id}`)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CODE_OR_NAME_REQUIRED | A code or a name is required | Neither sent. | — |\n| `409` | CODE_EXISTS | Code <code> already exists | The code is taken. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The discount detail (as `GET /stowbo/discounts/{id}`)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A code or a name is required — Neither sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A code or a name is required","path":"/stowbo/discounts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Code <code> already exists — The code is taken.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Code <code> already exists","path":"/stowbo/discounts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Discounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Upper-cased on save.","example":"SUMMER10"},"name":{"type":"string"},"description":{"type":"string"},"type":{"type":"string","enum":["percent","fixed"],"default":"percent"},"value":{"type":"number","description":"Must be more than zero on create.","example":10},"maxDiscount":{"type":"number"},"minCartValue":{"type":"number"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number"},"usageLimitPerCustomer":{"type":"number"},"stackable":{"type":"boolean"},"firstOrderOnly":{"type":"boolean"},"priority":{"type":"number"},"status":{"type":"string","example":"active"},"listings":{"type":"array","items":{"type":"string"},"description":"Listing names it applies to. Empty = every space."}}},"example":{"code":"WELCOME10","type":"percent","value":10,"usageLimitPerCustomer":1}}}}}},"/stowbo/discounts/{id}":{"get":{"operationId":"StowboController_discount","summary":"Get one discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Discount (`sf_discount`) sk or code.","example":"SUMMER10"}],"responses":{"200":{"description":"{ discount, record, redemptions }","content":{"application/json":{"schema":{"type":"object","properties":{"discount":{"type":"object","additionalProperties":true},"record":{"type":"object","additionalProperties":true},"redemptions":{"type":"array","items":{"type":"object","properties":{"booking":{"type":"string"},"reference":{"type":"string"},"customer":{"type":"string"},"customerName":{"type":"string","nullable":true},"amount":{"type":"number"},"at":{"type":"string"},"status":{"type":"string"},"currency":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such discount — No discount has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such discount","path":"/stowbo/discounts/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Discounts"],"description":"The discount, its raw record, and every booking that redeemed it, newest first.\n\n#### Signature\n\n```http\nGET /stowbo/discounts/{id} (id: string) -> { discount, record, redemptions }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DISCOUNT_NOT_FOUND | No such discount | No discount has that id or code. | List them with `GET /stowbo/discounts`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"StowboController_updateDiscount","summary":"Edit a discount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Discount (`sf_discount`) sk or code.","example":"SUMMER10"}],"responses":{"200":{"description":"The discount detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such discount — No discount has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such discount","path":"/stowbo/discounts/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Code <code> already exists — The new code is taken by another discount.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Code <code> already exists","path":"/stowbo/discounts/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Discounts"],"description":"Edits any discount — the platform's or a host's. Only the fields sent change; `listings` rescopes it.\n\n#### Signature\n\n```http\nPUT /stowbo/discounts/{id} (id: string, body) -> The discount detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DISCOUNT_NOT_FOUND | No such discount | No discount has that id or code. | List them with `GET /stowbo/discounts`. |\n| `409` | CODE_EXISTS | Code <code> already exists | The new code is taken by another discount. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Upper-cased on save.","example":"SUMMER10"},"name":{"type":"string"},"description":{"type":"string"},"type":{"type":"string","enum":["percent","fixed"],"default":"percent"},"value":{"type":"number","description":"Must be more than zero on create.","example":10},"maxDiscount":{"type":"number"},"minCartValue":{"type":"number"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number"},"usageLimitPerCustomer":{"type":"number"},"stackable":{"type":"boolean"},"firstOrderOnly":{"type":"boolean"},"priority":{"type":"number"},"status":{"type":"string","example":"active"},"listings":{"type":"array","items":{"type":"string"},"description":"Listing names it applies to. Empty = every space."}}},"example":{"value":15,"endDate":"2026-12-31T23:59:59.000Z"}}}}}},"/stowbo/discounts/{id}/{action}":{"post":{"operationId":"StowboController_discountStatus","summary":"Switch a discount on or off","description":"`activate` or `deactivate`. Deactivating stops the code working; bookings that used it keep their discount line.\n\n#### Signature\n\n```http\nPOST /stowbo/discounts/{id}/{action} (id: string, action: string) -> The discount detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DISCOUNT_NOT_FOUND | No such discount | No discount has that id or code. | List them with `GET /stowbo/discounts`. |\n| `400` | INVALID_ACTION | activate or deactivate | Any other action. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Discount (`sf_discount`) sk or code.","example":"SUMMER10"},{"name":"action","required":true,"in":"path","schema":{"type":"string","enum":["activate","deactivate"]},"example":"deactivate"}],"responses":{"201":{"description":"The discount detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"activate or deactivate — Any other action.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"activate or deactivate","path":"/stowbo/discounts/{id}/{action}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such discount — No discount has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such discount","path":"/stowbo/discounts/{id}/{action}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Discounts"]}},"/stowbo/customer/{id}":{"get":{"operationId":"StowboController_customer","summary":"Get one customer","description":"A customer summarised: contact from the customer record, bookings, booked, paid, owed, what is held now and how much of it is overstaying.\n\n#### Signature\n\n```http\nGET /stowbo/customer/{id} (id: string) -> The customer summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | CUSTOMER_REQUIRED | Which customer? | Empty id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"The customer identifier used on bookings — email, phone or customer name.","example":"ada@example.com"}],"responses":{"200":{"description":"The customer summary","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"bookings":{"type":"number"},"cancelled":{"type":"number"},"booked":{"type":"number"},"paid":{"type":"number"},"owed":{"type":"number"},"held":{"type":"number"},"overstaying":{"type":"number"},"firstBookingAt":{"type":"string","nullable":true},"lastBookingAt":{"type":"string","nullable":true},"currency":{"type":"string"}}}}}},"400":{"description":"Which customer? — Empty id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Which customer?","path":"/stowbo/customer/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/transactions/{ref}":{"get":{"operationId":"StowboController_transaction","summary":"Get one transaction","description":"The transaction in full: the shaped row, the raw record with every gateway id, refunds against it (or the charge it refunds), other money on the same booking, the booking, and the money events on its timeline.\n\n#### Signature\n\n```http\nGET /stowbo/transactions/{ref} (ref: string) -> { transaction, record, refunds, refundOf, onSameBooking, booking, timeline }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TRANSACTION_NOT_FOUND | No such transaction | No transaction matches. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","schema":{"type":"string"},"description":"Transaction (`sf_transaction`) sk or its gateway `ref`.","example":"pi_3Abc123"}],"responses":{"200":{"description":"{ transaction, record, refunds, refundOf, onSameBooking, booking, timeline }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such transaction — No transaction matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such transaction","path":"/stowbo/transactions/{ref}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Ledger"]}},"/stowbo/transactions/{ref}/refund":{"post":{"operationId":"StowboController_refundTransaction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","schema":{"type":"string"},"description":"Transaction (`sf_transaction`) sk or its gateway `ref`.","example":"pi_3Abc123"}],"responses":{"201":{"description":"The refund","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"refund":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"refundedTotal":{"type":"number"},"fullyRefunded":{"type":"boolean"},"remaining":{"type":"number"}}}}}},"400":{"description":"Only a paid transaction can be refunded (this is <status>). — The transaction did not succeed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a paid transaction can be refunded (this is <status>).","path":"/stowbo/transactions/{ref}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction <ref> not found — No transaction matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction <ref> not found","path":"/stowbo/transactions/{ref}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Ledger"],"summary":"Refund a transaction","description":"Refunds a paid transaction through the gateway, in part or in full — omit `amount` to refund whatever remains. Writes a linked `refund` row and marks the original refunded or partially refunded; its amount is never changed.\n\n#### Signature\n\n```http\nPOST /stowbo/transactions/{ref}/refund (ref: string, body) -> The refund\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TRANSACTION_NOT_FOUND | Transaction <ref> not found | No transaction matches. | — |\n| `400` | NOT_PAID | Only a paid transaction can be refunded (this is <status>). | The transaction did not succeed. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/transactions/{ref}`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Defaults to the remaining refundable amount."},"reason":{"type":"string"}}},"example":{"amount":10,"reason":"Goodwill"}}}}}},"/stowbo/transactions/{requestId}/cancel":{"post":{"operationId":"StowboController_cancelTransaction","summary":"Void a payment request","description":"Voids a still-unpaid payment request so its link and QR stop working. Cancelling one already cancelled returns `alreadyCancelled`. A paid request is refused — refund it instead.\n\n#### Signature\n\n```http\nPOST /stowbo/transactions/{requestId}/cancel (requestId: string) -> { ok, cancelled } or { ok, alreadyCancelled }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUEST_NOT_FOUND | Payment request <id> not found | No request with that id. | — |\n| `400` | ALREADY_PAID | That payment is already paid — refund it instead of cancelling. | The request was paid. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Payment request (`sf_transaction`) sk.","example":"66f1a2b3c4d5e6f708192a3e"}],"responses":{"201":{"description":"{ ok, cancelled } or { ok, alreadyCancelled }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"That payment is already paid — refund it instead of cancelling. — The request was paid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"That payment is already paid — refund it instead of cancelling.","path":"/stowbo/transactions/{requestId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payment request <id> not found — No request with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payment request <id> not found","path":"/stowbo/transactions/{requestId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Ledger"]}},"/stowbo/pulse":{"get":{"operationId":"StowboController_pulse","summary":"Get the platform right now","description":"What is true across the platform this second: custody (in custody, present, arriving, departing, overstaying, awaiting check-out, oldest overstay), work waiting (open requests, held payments, pending host applications), demand (live and unpaid bookings) and supply (hosts and spaces).\n\n#### Signature\n\n```http\nGET /stowbo/pulse () -> The now-strip\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The now-strip","content":{"application/json":{"schema":{"type":"object","properties":{"at":{"type":"string","format":"date-time"},"custody":{"type":"object","properties":{"inCustody":{"type":"number"},"present":{"type":"number"},"arriving":{"type":"number"},"departing":{"type":"number"},"overstaying":{"type":"number"},"awaitingCheckout":{"type":"number"},"oldestOverstayDays":{"type":"number"}}},"work":{"type":"object","properties":{"openRequests":{"type":"number"},"paymentsHeld":{"type":"number"},"pendingHostApplications":{"type":"number"}}},"demand":{"type":"object","properties":{"liveBookings":{"type":"number"},"unpaidBookings":{"type":"number"},"unpaidValue":{"type":"number"}}},"supply":{"type":"object","properties":{"hosts":{"type":"number"},"listingHosts":{"type":"number"},"spaces":{"type":"number"},"liveSpaces":{"type":"number"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/items":{"get":{"operationId":"StowboController_items","summary":"List every item on the platform","description":"Booking items — not orders: twelve units under one order can be in twelve places at once. Each row says whose it is, where it is and where it stands. Filtered, sorted and paged on the server.\n\n#### Signature\n\n```http\nGET /stowbo/items (lane?: string, host?: string, listing?: string, customer?: string, status?: string, search?: string, from?: string, to?: string, page?: integer, pageSize?: integer, sort?: string, sortType?: string) -> A page of items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":50},"description":"At most 500.","example":50},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string"}},{"name":"customer","required":false,"in":"query","schema":{"type":"string"}},{"name":"listing","required":false,"in":"query","schema":{"type":"string"}},{"name":"host","required":false,"in":"query","schema":{"type":"string"}},{"name":"lane","required":false,"in":"query","schema":{"type":"string","enum":["custody","arriving","departing","overstaying","awaitingCheckout","present","gone","requests"]}},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"sort","in":"query","required":false,"schema":{"type":"string"}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"A page of items","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Booking items"},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/money":{"get":{"operationId":"StowboController_money","summary":"See where the money is","description":"Booked money split into paid, in flight (payment links out) and owed, with the pots underneath — authorised-not-captured, deposits, unpaid balances, unopened links, unsettled lines, accruing overstay, fees, wallets, frozen funds, payouts, refunds and failed refunds — and the flow from captured to platform fee to host earnings. Scope it to a host, customer or booking; `from`/`to` limit it to bookings made and transactions dated in the period.\n\n#### Signature\n\n```http\nGET /stowbo/money (host?: string, listing?: string, customer?: string, booking?: string, from?: string, to?: string) -> { at, scope, period, takeRate, booked, kinds: { paid, inFlight, owed }, pots, flow, … }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"Period end (ISO date).","example":"2026-09-30"},{"name":"from","required":false,"in":"query","description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"booking","required":false,"in":"query","schema":{"type":"string"}},{"name":"customer","required":false,"in":"query","schema":{"type":"string"}},{"name":"host","required":false,"in":"query","schema":{"type":"string"}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ at, scope, period, takeRate, booked, kinds: { paid, inFlight, owed }, pots, flow, … }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/calendar":{"get":{"operationId":"StowboController_platformCalendar","summary":"Get the platform calendar","description":"Every space × every day across every host, each stay a bar — what a front desk works from. Capacity per day from the space, usage from the items whose window covers the day, overstay running past the bar. Pass `to` or `days` for the window.\n\n#### Signature\n\n```http\nGET /stowbo/calendar (from?: string, to?: string, days?: integer, host?: string, listing?: string, parent?: string, customer?: string, status?: string, search?: string, sort?: string, sortType?: string, hideEmpty?: boolean) -> { from, to, days, today, at, scope, rows, totals }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_FROM | Bad `from` | `from` is not a date. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/listing/{listing}/calendar`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"parent","required":false,"in":"query","schema":{"type":"string"},"description":"Spaces under this listing."},{"name":"listing","required":false,"in":"query","schema":{"type":"string"}},{"name":"host","required":false,"in":"query","schema":{"type":"string"}},{"name":"days","required":false,"in":"query","schema":{"type":"integer"}},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"First day. Defaults to today."},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"busiest"}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string"}},{"name":"hideEmpty","in":"query","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"{ from, to, days, today, at, scope, rows, totals }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Bad `from` — `from` is not a date.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Bad `from`","path":"/stowbo/calendar","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/booking":{"post":{"operationId":"StowboController_createBooking","summary":"Book on a customer's behalf","description":"Creates a booking through the same path the customer app takes, recorded with channel `operator`. Availability is re-checked for every line and the booking is refused if any line is full. Platform and host fees and any discount code are priced in. The amount due now is charged to `paymentMethodId` unless `skipPayment` is set, in which case the booking is recorded as owed.\n\n#### Signature\n\n```http\nPOST /stowbo/booking (body) -> The booking, its items, the total and what was charged now\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | LINES_REQUIRED | At least one line is required | `lines` is empty. | — |\n| `409` | UNAVAILABLE | <listing> is not available for that window — N free, M requested | A line does not fit. | Check `GET /stowbo/availability`. |\n| `402` | PAYMENT_REQUIRED | A payment method is required — <amount> <currency> due now. | Something is due now, no `paymentMethodId` and no `skipPayment`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /stowbo/availability`\n- `POST /stowbo/booking/{bookingId}/payment-request`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The booking, its items, the total and what was charged now","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"type":"object","additionalProperties":true},"items":{"type":"array","items":{"type":"object","additionalProperties":true}},"total":{"type":"number"},"dueNow":{"type":"number"},"paid":{"type":"boolean"}}}}}},"400":{"description":"At least one line is required — `lines` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"At least one line is required","path":"/stowbo/booking","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"description":"A payment method is required — <amount> <currency> due now. — Something is due now, no `paymentMethodId` and no `skipPayment`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"A payment method is required — <amount> <currency> due now.","path":"/stowbo/booking","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"<listing> is not available for that window — N free, M requested — A line does not fit.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"<listing> is not available for that window — N free, M requested","path":"/stowbo/booking","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["lines"],"properties":{"lines":{"type":"array","items":{"type":"object","required":["listing","startDate"],"properties":{"listing":{"type":"string"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time","nullable":true,"description":"Omit for an open stay."},"quantity":{"type":"number","default":1},"addOns":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"customer":{"type":"string","description":"The customer identifier — email, phone or customer name.","example":"ada@example.com"},"guest":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"description":"Who is at the counter, when they have no account."},"note":{"type":"string"},"currency":{"type":"string","example":"USD"},"paymentMethodId":{"type":"string","description":"Required when something is due now and `skipPayment` is not set."},"skipPayment":{"type":"boolean","description":"Record the booking as owed instead of charging now."},"discountCode":{"type":"string"},"checkedIn":{"type":"boolean","description":"Arrives checked in (walk-in)."},"unit":{"type":"string"},"identifier":{"type":"string"},"units":{"type":"array","items":{"type":"object","properties":{"identifier":{"type":"string"},"unit":{"type":"string"},"type":{"type":"string"}}}},"cancellationPolicy":{"type":"object","additionalProperties":true}}},"example":{"customer":"ada@example.com","lines":[{"listing":"downtown-lockers","startDate":"2026-09-05T09:00:00.000Z","endDate":"2026-09-08T17:00:00.000Z","quantity":1}],"skipPayment":true}}}}}},"/stowbo/activity":{"get":{"operationId":"StowboController_activity","summary":"List every booking timeline, merged","description":"Every booking's timeline events, merged and time-ordered, with counts by event type.\n\n#### Signature\n\n```http\nGET /stowbo/activity (host?: string, listing?: string, booking?: string, customer?: string, type?: string, actorRole?: string, actor?: string, since?: string, from?: string, to?: string, search?: string, page?: integer, pageSize?: integer) -> A page of events and `byType` counts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"booking","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string"}},{"name":"actorRole","in":"query","required":false,"schema":{"type":"string"}},{"name":"actor","in":"query","required":false,"schema":{"type":"string"}},{"name":"since","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"A page of events and `byType` counts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Timeline events"},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"},"byType":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/funnel":{"get":{"operationId":"StowboController_funnel","summary":"Get the booking funnel","description":"Cart → details → payment → held → booked → turned up → collected, with drop-off against the stage before. Cart outcomes (converted, abandoned, expired, still open, where they left, why) and bookings by channel. Walk-ins are counted separately — they never touched a cart.\n\n#### Signature\n\n```http\nGET /stowbo/funnel (host?: string, listing?: string, since?: string, from?: string, to?: string) -> { scope, since, stages, carts, bookings, gmv, … }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"since","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"}],"responses":{"200":{"description":"{ scope, since, stages, carts, bookings, gmv, … }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/hosts":{"get":{"operationId":"StowboController_hosts","summary":"Get the host scorecard","description":"Every host at once: earned, pending, spaces, custody, overstay rate and cancel rate, plus pending applications.\n\n#### Signature\n\n```http\nGET /stowbo/hosts (from?: string, to?: string, search?: string, status?: string, sort?: string, sortType?: string) -> { period, hosts, totals }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"Period end (ISO date).","example":"2026-09-30"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"Period start (ISO date).","example":"2026-09-01"},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string"}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ period, hosts, totals }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/addons":{"get":{"operationId":"StowboController_addons","summary":"Get the add-on catalogue","description":"Every add-on, the spaces that offer or inherit it, what it has sold, and the ones nobody offers (orphaned).\n\n#### Signature\n\n```http\nGET /stowbo/addons (host?: string, from?: string, to?: string, search?: string, status?: string) -> { addons, totals: { catalog, orphaned, neverBought, revenue } }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ addons, totals: { catalog, orphaned, neverBought, revenue } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/search":{"get":{"operationId":"StowboController_search","summary":"Search everything","description":"A plate, a locker number, a booking reference, an email, a space or a host — one query across spaces, hosts, customers, items and bookings. Fewer than two characters returns empty groups.\n\n#### Signature\n\n```http\nGET /stowbo/search (q?: string) -> { query, spaces, hosts, customers, items, bookings } — each a list of { kind, id, label, detail }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"q","required":true,"in":"query","schema":{"type":"string"},"example":"STW-4821"}],"responses":{"200":{"description":"{ query, spaces, hosts, customers, items, bookings } — each a list of { kind, id, label, detail }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/audience":{"get":{"operationId":"StowboController_audience","summary":"Resolve a message audience","description":"Turns a described group into real recipients, de-duplicated by email/phone. Groups: `hosts.all`, `hosts.pending`, `hosts.overstaying`, `guests.inCustody`, `guests.overstaying`, `guests.unpaid`, `booking.both` (the booking's guest and host). An unknown group returns no recipients.\n\n#### Signature\n\n```http\nGET /stowbo/audience (group?: string, listing?: string, host?: string, booking?: string, days?: integer) -> { group, recipients, count }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"group","required":true,"in":"query","schema":{"type":"string","enum":["hosts.all","hosts.pending","hosts.overstaying","guests.inCustody","guests.overstaying","guests.unpaid","booking.both"]}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"booking","in":"query","required":false,"description":"Required for `booking.both`.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"{ group, recipients, count }","content":{"application/json":{"schema":{"type":"object","properties":{"group":{"type":"string"},"recipients":{"type":"array","items":{"type":"object","additionalProperties":true}},"count":{"type":"number"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/listing/{listing}/capacity":{"post":{"operationId":"StowboController_capCapacity","summary":"Cap a space's capacity for a window","description":"A blackout with a number: the space stays open but takes at most `capacity` for the window. `capacity: 0` closes it. The window defaults to the next 24 hours from now. Bookings already inside it come back as `conflicts`, not cancelled.\n\n#### Signature\n\n```http\nPOST /stowbo/listing/{listing}/capacity (listing: string, body) -> The closure, all closures, and bookings caught inside it — as `POST /stowbo/listing/{listing}/blackouts`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |\n| `400` | INVALID_WINDOW | to must be after from | `to` is at or before `from`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/listing/{listing}/blackouts`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"path","schema":{"type":"string"},"description":"Listing `name`. A site aggregates its child listings; a leaf listing stands alone.","example":"downtown-lockers"}],"responses":{"201":{"description":"The closure, all closures, and bookings caught inside it — as `POST /stowbo/listing/{listing}/blackouts`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"to must be after from — `to` is at or before `from`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"to must be after from","path":"/stowbo/listing/{listing}/capacity","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Listing <name> not found — No listing in the org has that name or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing <name> not found","path":"/stowbo/listing/{listing}/capacity","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Calendar"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"capacity":{"type":"number","default":0,"example":5},"from":{"type":"string","format":"date-time","description":"Defaults to now."},"to":{"type":"string","format":"date-time","description":"Defaults to `from` + 24 hours."},"reason":{"type":"string","default":"servicing"},"note":{"type":"string"},"units":{"type":"array","items":{"type":"string"}}}},"example":{"capacity":5,"from":"2026-10-01T00:00:00.000Z","to":"2026-10-03T00:00:00.000Z","reason":"event"}}}}}},"/stowbo/listing/{listing}/occupancy-now":{"get":{"operationId":"StowboController_occupancyNow","summary":"See what is in a space right now","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":true,"in":"path","schema":{"type":"string"},"description":"Listing `name`. A site aggregates its child listings; a leaf listing stands alone.","example":"downtown-lockers"}],"responses":{"200":{"description":"What is here","content":{"application/json":{"schema":{"type":"object","properties":{"listing":{"type":"string"},"at":{"type":"string","format":"date-time"},"occupied":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string"},"unit":{"type":"string","nullable":true},"identifier":{"type":"string","nullable":true},"booking":{"type":"string"},"customer":{"type":"string"},"since":{"type":"string","nullable":true},"until":{"type":"string","nullable":true},"overstaying":{"type":"boolean"}}}}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"The items physically present in a listing now (active or disputed booking items whose last movement was in), with who they belong to and whether they are overstaying.\n\n#### Signature\n\n```http\nGET /stowbo/listing/{listing}/occupancy-now (listing: string) -> What is here\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The listing name is not checked — an unknown listing returns nothing occupied, not a 404.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/stowbo/bookings":{"get":{"operationId":"StowboController_bookings","summary":"List bookings","description":"Every Stowbo booking, filtered, sorted and paged on the server, with totals for the whole filtered set.\n\n#### Signature\n\n```http\nGET /stowbo/bookings (search?: string, status?: string, paymentStatus?: string, host?: string, listing?: string, customer?: string, coupon?: string, from?: string, to?: string, window?: boolean, via?: string, page?: integer, pageSize?: integer, sort?: string, sortType?: string) -> A page of bookings and totals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"paymentStatus","in":"query","required":false,"schema":{"type":"string"}},{"name":"host","in":"query","required":false,"schema":{"type":"string"}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer","in":"query","required":false,"schema":{"type":"string"}},{"name":"coupon","in":"query","required":false,"description":"A discount code, or `any` for every booking that used one.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Period start (ISO date).","schema":{"type":"string"},"example":"2026-09-01"},{"name":"to","in":"query","required":false,"description":"Period end (ISO date).","schema":{"type":"string"},"example":"2026-09-30"},{"name":"window","in":"query","required":false,"description":"Filter by stay window instead of booking date.","schema":{"type":"boolean"}},{"name":"via","in":"query","required":false,"description":"Booking channel.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","in":"query","required":false,"description":"At most 500.","schema":{"type":"integer","default":50},"example":50},{"name":"sort","in":"query","required":false,"schema":{"type":"string"}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"A page of bookings and totals","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Bookings"},"total":{"type":"number"},"page":{"type":"number"},"pageSize":{"type":"number"},"totals":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Oversight"]}},"/stowbo/booking/{bookingId}/payment-request":{"post":{"operationId":"StowboController_paymentRequest","summary":"Send a payment link for a booking","description":"Raises a payment request as the booking's host and sends the link to the customer. Defaults to the booking's balance and the booking's customer; `send: false` creates the link without sending it.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/payment-request (bookingId: string, body) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | AMOUNT_REQUIRED | amount is required | Nothing is owed and no `amount` was sent. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/transactions/{requestId}/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"The request","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"url":{"type":"string"},"paymentRequestId":{"type":"string"},"status":{"type":"string"},"sent":{"type":"boolean"},"sentTo":{"type":"array","items":{"type":"string"}},"amount":{"type":"number"},"currency":{"type":"string"},"category":{"type":"string"},"booking":{"type":"string"}}}}}},"400":{"description":"amount is required — Nothing is owed and no `amount` was sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"amount is required","path":"/stowbo/booking/{bookingId}/payment-request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/payment-request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Defaults to the balance."},"category":{"type":"string","default":"balance"},"email":{"type":"string"},"phone":{"type":"string"},"description":{"type":"string"},"send":{"type":"boolean","default":true}}},"example":{"send":true}}}}}},"/stowbo/booking/{bookingId}/take-payment":{"post":{"operationId":"StowboController_takePayment","summary":"Record a payment taken on a booking","description":"Records a card payment taken for the booking, as its host, from a confirmed `paymentIntentId` (card reader or Tap to Pay). The intent is verified with the gateway; recording the same intent twice returns `alreadyRecorded`. The operator never types card details.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/take-payment (bookingId: string, body) -> { ok, paymentRef, amount, currency, category, booking } or { ok, alreadyRecorded, paymentRef }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n| `400` | PAYMENT_INTENT_REQUIRED | paymentIntentId is required | No intent sent. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"{ ok, paymentRef, amount, currency, category, booking } or { ok, alreadyRecorded, paymentRef }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"paymentIntentId is required — No intent sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"paymentIntentId is required","path":"/stowbo/booking/{bookingId}/take-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/take-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Money"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentIntentId"],"properties":{"paymentIntentId":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"category":{"type":"string","default":"charge","description":"`tip` records a tip."},"channel":{"type":"string","enum":["card_reader","tap_to_pay"]},"email":{"type":"string"},"phone":{"type":"string"},"reference":{"type":"string"},"description":{"type":"string"}}},"example":{"paymentIntentId":"pi_3Abc123","category":"balance"}}}}}},"/stowbo/transactions/{ref}/receipt":{"post":{"operationId":"StowboController_receipt","summary":"Resend a receipt","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","schema":{"type":"string"},"description":"Transaction (`sf_transaction`) sk or its gateway `ref`.","example":"pi_3Abc123"}],"responses":{"201":{"description":"{ ok, sentTo }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction <ref> not found — No transaction matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction <ref> not found","path":"/stowbo/transactions/{ref}/receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Ledger"],"description":"Emails and/or texts the receipt for a transaction. With no email or phone it goes to the linked booking's customer.\n\n#### Signature\n\n```http\nPOST /stowbo/transactions/{ref}/receipt (ref: string, body) -> { ok, sentTo }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | TRANSACTION_NOT_FOUND | Transaction <ref> not found | No transaction matches. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"phone":{"type":"string"},"bookingId":{"type":"string"}}}}}}}},"/stowbo/items/{itemId}/checkin":{"post":{"operationId":"StowboController_itemCheckIn","summary":"Check an item in","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"`stowbo_booking_item` sk — one thing booked, not the whole order.","example":"66f1a2b3c4d5e6f708192a3c"}],"responses":{"201":{"description":"The booking item","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item <id> not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item <id> not found","path":"/stowbo/items/{itemId}/checkin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"That session is already closed — The item was already checked out.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"That session is already closed","path":"/stowbo/items/{itemId}/checkin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"Takes custody of a booking item on the host's behalf and optionally assigns its unit or identifier.\n\n#### Signature\n\n```http\nPOST /stowbo/items/{itemId}/checkin (itemId: string, body) -> The booking item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Booking item <id> not found | No booking item has that id. | Find it with `GET /stowbo/items`. |\n| `409` | SESSION_CLOSED | That session is already closed | The item was already checked out. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"},"unit":{"type":"string"},"identifier":{"type":"string"}}}}}}}},"/stowbo/items/{itemId}/checkout":{"post":{"operationId":"StowboController_itemCheckOut","summary":"Check an item out","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"`stowbo_booking_item` sk — one thing booked, not the whole order.","example":"66f1a2b3c4d5e6f708192a3c"}],"responses":{"201":{"description":"The item, its statement, and `orderComplete`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item <id> not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item <id> not found","path":"/stowbo/items/{itemId}/checkout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already checked out — The session is already closed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already checked out","path":"/stowbo/items/{itemId}/checkout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"Closes the item's custody session — the only place an item stops occupying capacity. What it cost is added to the order; the order completes when every item has checked out. Refused while the item is still present: record the move-out first.\n\n#### Signature\n\n```http\nPOST /stowbo/items/{itemId}/checkout (itemId: string, body) -> The item, its statement, and `orderComplete`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Booking item <id> not found | No booking item has that id. | Find it with `GET /stowbo/items`. |\n| `409` | ALREADY_CHECKED_OUT | Already checked out | The session is already closed. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"}}}}}}},"get":{"operationId":"StowboController_itemCheckoutStatement","summary":"Get what an item owes before check-out","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"`stowbo_booking_item` sk — one thing booked, not the whole order.","example":"66f1a2b3c4d5e6f708192a3c"}],"responses":{"200":{"description":"The statement","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"string"},"listing":{"type":"string"},"unit":{"type":"string","nullable":true},"identifier":{"type":"string","nullable":true},"startDate":{"type":"string"},"endDate":{"type":"string","nullable":true},"open":{"type":"boolean"},"usedUntil":{"type":"string"},"present":{"type":"boolean"},"canCheckOut":{"type":"boolean"},"accruing":{"type":"boolean"},"used":{"type":"number"},"overstay":{"type":"number"},"rateLines":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item <id> not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item <id> not found","path":"/stowbo/items/{itemId}/checkout","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"The check-out statement for one item: time used, any overstay, and the rate lines — without closing anything. `canCheckOut` is false while the item is still present.\n\n#### Signature\n\n```http\nGET /stowbo/items/{itemId}/checkout (itemId: string) -> The statement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Booking item <id> not found | No booking item has that id. | Find it with `GET /stowbo/items`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/items/{itemId}/checkout`"}},"/stowbo/items/{itemId}/movement":{"post":{"operationId":"StowboController_itemMovement","summary":"Record an item going in or out","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"`stowbo_booking_item` sk — one thing booked, not the whole order.","example":"66f1a2b3c4d5e6f708192a3c"}],"responses":{"201":{"description":"The booking item","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"direction must be 'in' or 'out' — Missing or other value.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"direction must be 'in' or 'out'","path":"/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item <id> not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item <id> not found","path":"/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"That session is closed — The item was checked out.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"That session is closed","path":"/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"Records one movement within the custody session. A move-out is not a check-out: the space stays held. A movement that matches an open guest request completes it.\n\n#### Signature\n\n```http\nPOST /stowbo/items/{itemId}/movement (itemId: string, body) -> The booking item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Booking item <id> not found | No booking item has that id. | Find it with `GET /stowbo/items`. |\n| `400` | INVALID_DIRECTION | direction must be 'in' or 'out' | Missing or other value. | — |\n| `409` | SESSION_CLOSED | That session is closed | The item was checked out. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["direction"],"properties":{"direction":{"type":"string","enum":["in","out"]},"photos":{"type":"array","items":{"type":"object","additionalProperties":true}},"note":{"type":"string"},"condition":{"type":"string"},"signature":{"type":"string"},"declaredValue":{"type":"number"},"releasedTo":{"type":"string"},"collectedBy":{"type":"string"},"verified":{"type":"array","items":{"type":"string"}},"unit":{"type":"string"},"identifier":{"type":"string"}}},"example":{"direction":"out","note":"Guest collecting overnight bag"}}}}}},"/stowbo/items/{itemId}/assign":{"post":{"operationId":"StowboController_itemAssign","summary":"Assign an item to a unit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"`stowbo_booking_item` sk — one thing booked, not the whole order.","example":"66f1a2b3c4d5e6f708192a3c"}],"responses":{"201":{"description":"The booking item","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"unit is required — No unit.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"unit is required","path":"/stowbo/items/{itemId}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item <id> not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item <id> not found","path":"/stowbo/items/{itemId}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Custody"],"description":"Puts the item in a unit of its listing, or moves it to a different one. The customer is told the new spot.\n\n#### Signature\n\n```http\nPOST /stowbo/items/{itemId}/assign (itemId: string, body) -> The booking item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ITEM_NOT_FOUND | Booking item <id> not found | No booking item has that id. | Find it with `GET /stowbo/items`. |\n| `400` | UNIT_REQUIRED | unit is required | No unit. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["unit"],"properties":{"unit":{"type":"string","example":"L-22"},"note":{"type":"string"}}}}}}}},"/stowbo/booking/{bookingId}/requests/{requestId}":{"post":{"operationId":"StowboController_respondToRequest","summary":"Answer a customer request","description":"Moves a request the host has left sitting: `acknowledged` → `ready` → `done`, or `declined`. The customer is told either way.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/requests/{requestId} (bookingId: string, requestId: string, body) -> { ok, request }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Request id from the booking.","example":"req_1756476202118"}],"responses":{"201":{"description":"{ ok, request }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/requests/{requestId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["acknowledged","ready","done","declined"]}}},"example":{"status":"ready"}}}}}},"/stowbo/booking/{bookingId}/request":{"post":{"operationId":"StowboController_raiseRequest","summary":"Raise a request for a customer","description":"For a customer who phoned in: raises the same request the app would (`out`, `in`, `checkout`, `addon` or `service`), puts it on the booking timeline and notifies the host.\n\n#### Signature\n\n```http\nPOST /stowbo/booking/{bookingId}/request (bookingId: string, body) -> { ok, request }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <id> not found | No booking in the org matches that id or reference. | Check the identifier. `GET /stowbo/custody` lists what is currently held, with its booking id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /stowbo/booking/{bookingId}/requests/{requestId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking `sk` or reference name.","example":"STW-4821"}],"responses":{"201":{"description":"{ ok, request }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"request":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking <id> not found — No booking in the org matches that id or reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <id> not found","path":"/stowbo/booking/{bookingId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Stay"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["out","in","checkout","addon","service"]},"note":{"type":"string"},"item":{"type":"string","description":"Booking-item id it concerns."},"label":{"type":"string"}}},"example":{"type":"out","note":"Collecting at 5pm"}}}}}},"/client/stowbo/init":{"get":{"operationId":"StowboClientController_init","summary":"Get app launch data","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Identity and host status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/init","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Who the caller is and whether they are a host — the single call an app makes on launch to decide which surface to show.\n\n#### Signature\n\n```http\nGET /client/stowbo/init () -> Identity and host status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/me`"}},"/client/stowbo/listings":{"get":{"operationId":"StowboClientController_browse","summary":"Search places","description":"Finds places to book. **Dates change what this means.** With `startDate` and `endDate` the results are restricted to what can actually take the booking for that window — full, blacked-out and tree-blocked listings are excluded — and each result carries a real price for that stay.\n\nWithout dates it is a plain catalogue browse making **no availability or price claims**, because a banded rate has no single price to advertise. A UI that shows prices from a dateless search is showing something the server did not promise.\n\nSearch geographically by viewport (`swLat`/`swLng`/`neLat`/`neLng`, sent together as the map pans) or by point (`lat`/`lng` with `radiusKm`).\n\n#### Signature\n\n```http\nGET /client/stowbo/listings (city?: string, spaceType?: string, keyword?: string, startDate?: string, endDate?: string, quantity?: integer, lat?: number, lng?: number, radiusKm?: number, swLat?: number, swLng?: number, neLat?: number, neLng?: number, page?: integer, pageSize?: integer) -> Matching listings — priced only when dates were given\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Without dates, results carry no availability or price guarantee.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NO_COORDINATES | No coordinates to search from | A geographic search was requested without a usable point or viewport. | Send `lat`/`lng`, or all four viewport corners. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/listings/{listingId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"city","required":false,"in":"query","schema":{"type":"string"},"example":"San Francisco"},{"name":"spaceType","required":false,"in":"query","schema":{"type":"string"},"example":"parking"},{"name":"keyword","required":false,"in":"query","schema":{"type":"string"},"example":"covered"},{"name":"startDate","required":false,"in":"query","description":"ISO date. With `endDate`, filters to what is genuinely bookable then and prices it for that window.","schema":{"type":"string"},"example":"2026-09-05"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-09-12"},{"name":"quantity","required":false,"in":"query","schema":{"type":"integer"},"description":"How many are needed.","example":1},{"name":"lat","required":false,"in":"query","description":"\"Near me\" — a point instead of a viewport.","schema":{"type":"number"},"example":37.7749},{"name":"lng","required":false,"in":"query","schema":{"type":"number"},"example":-122.4194},{"name":"radiusKm","required":false,"in":"query","description":"With `lat`/`lng`. Default 10, max 200.","schema":{"type":"number"},"example":10},{"name":"swLat","required":false,"in":"query","description":"Map viewport corner — send all four as the customer pans and zooms.","schema":{"type":"number"},"example":37.7},{"name":"swLng","required":false,"in":"query","schema":{"type":"number"},"example":-122.52},{"name":"neLat","required":false,"in":"query","schema":{"type":"number"},"example":37.83},{"name":"neLng","required":false,"in":"query","schema":{"type":"number"},"example":-122.35},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Matching listings — priced only when dates were given","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"No coordinates to search from — A geographic search was requested without a usable point or viewport.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No coordinates to search from","path":"/client/stowbo/listings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"]}},"/client/stowbo/listings/{listingId}/nearby":{"get":{"operationId":"StowboClientController_nearby","summary":"Find similar places nearby","description":"Comparable places around a listing. Defaults to the anchor listing's own space type and coordinates; pass `lat`/`lng` to search from a different point, and dates to get back only what is free then, priced for it.\n\n#### Signature\n\n```http\nGET /client/stowbo/listings/{listingId}/nearby (listingId: string, radiusKm?: number, spaceType?: string, lat?: number, lng?: number, startDate?: string, endDate?: string, quantity?: integer, pageSize?: integer) -> Nearby listings\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/listings/{listingId}/more-from-host`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"},{"name":"radiusKm","required":false,"in":"query","description":"Default 5, max 200.","schema":{"type":"number"},"example":5},{"name":"spaceType","required":false,"in":"query","schema":{"type":"string"},"description":"Defaults to the anchor listing's type.","example":"parking"},{"name":"lat","required":false,"in":"query","schema":{"type":"number"},"example":37.7749},{"name":"lng","required":false,"in":"query","schema":{"type":"number"},"example":-122.4194},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date. With `endDate`, filters to what is genuinely bookable then and prices it for that window.","example":"2026-09-05"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-09-12"},{"name":"quantity","required":false,"in":"query","schema":{"type":"integer"},"description":"How many are needed.","example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":12}],"responses":{"200":{"description":"Nearby listings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/listings/{listingId}/nearby","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"]}},"/client/stowbo/listings/{listingId}/more-from-host":{"get":{"operationId":"StowboClientController_moreFromHost","summary":"Get other places by the same host","description":"This host's other active listings, excluding the current one, plus a guest-safe host card. Pass dates to get them availability-filtered and priced for the window.\n\n#### Signature\n\n```http\nGET /client/stowbo/listings/{listingId}/more-from-host (listingId: string, startDate?: string, endDate?: string, quantity?: integer, page?: integer, pageSize?: integer) -> The host's other listings and their card\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/listings/{listingId}/nearby`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date. With `endDate`, filters to what is genuinely bookable then and prices it for that window.","example":"2026-09-05"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-09-12"},{"name":"quantity","required":false,"in":"query","schema":{"type":"integer"},"description":"How many are needed.","example":1},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"The host's other listings and their card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/listings/{listingId}/more-from-host","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"]}},"/client/stowbo/listings/{listingId}":{"get":{"operationId":"StowboClientController_listing","summary":"Get a place","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"The listing with its units and add-ons","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/listings/{listingId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"],"description":"A listing, what is bookable inside it, and the add-ons that apply to it.\n\n#### Signature\n\n```http\nGET /client/stowbo/listings/{listingId} (listingId: string) -> The listing with its units and add-ons\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/listings/{listingId}/availability`"}},"/client/stowbo/chat-config":{"get":{"operationId":"StowboClientController_chatConfig","summary":"Get the support chat settings","description":"The chat config id and app id the Stowbo site points at. The app opens its customer-to-admin support chat with these; an admin changes them on the site.\n\n#### Signature\n\n```http\nGET /client/stowbo/chat-config () -> Chat settings (each null when not set)\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"addOns","required":false,"in":"query","description":"JSON array of chosen add-ons, for the quote","schema":{}},{"name":"quantity","required":false,"in":"query","schema":{}},{"name":"endDate","required":true,"in":"query","schema":{}},{"name":"startDate","required":true,"in":"query","schema":{}},{"name":"listingId","required":true,"in":"path","schema":{}}],"responses":{"200":{"description":"Chat settings (each null when not set)","content":{"application/json":{"schema":{"type":"object","properties":{"configId":{"type":"string","nullable":true},"appId":{"type":"string","nullable":true},"site":{"type":"string","nullable":true}}},"example":{"configId":"66f1c0ffee","appId":"stowbo-support","site":"stowbo"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"]}},"/client/stowbo/bookings/{bookingId}/delegations":{"post":{"operationId":"StowboClientController_createDelegation","summary":"Hand my pickup to someone else","description":"Creates a hand-off for some of my items: its own pickup code and pass link, sent to the collector by SMS and/or email. The owner gets the link back too, to share it themselves. Each item must be on the booking, still in custody, and not already on another live hand-off.\n\n#### Signature\n\n```http\nPOST /client/stowbo/bookings/{bookingId}/delegations (bookingId: string, body) -> The hand-off, with `passUrl`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n| `400` | ITEMS_REQUIRED | Pick at least one item to hand off | `items` is empty. | — |\n| `409` | BOOKING_NOT_ACTIVE | This booking is no longer active | Cancelled, no-show, settled or completed. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/bookings/{bookingId}/delegations`\n- `DELETE /client/stowbo/delegations/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The hand-off, with `passUrl`","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"Returned on create and resend only."}}}}}},"400":{"description":"Pick at least one item to hand off — `items` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick at least one item to hand off","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This booking is no longer active — Cancelled, no-show, settled or completed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This booking is no longer active","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"string"},"description":"Booking-item ids to hand off."},"name":{"type":"string","description":"The collector's name.","example":"Sam Doe"},"phone":{"type":"string","description":"A phone or an email is required.","example":"+15125550100"},"email":{"type":"string","example":"sam@example.com"},"verify":{"type":"string","enum":["qr","qr_name","qr_id"],"default":"qr_id","description":"What the host checks at pickup: QR only, QR + name, or QR + photo ID."},"validUntil":{"type":"string","format":"date-time","description":"Defaults to, and is capped at, the latest end date of the items."},"note":{"type":"string"},"photo":{"type":"string","description":"Photo of the collector."}}},"example":{"items":["ITM-4821"],"name":"Sam Doe","phone":"+15125550100","verify":"qr_id"}}}}},"get":{"operationId":"StowboClientController_myDelegations","summary":"List the hand-offs on my booking","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"Hand-offs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"Returned on create and resend only."}}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/delegations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Every hand-off on the booking, newest first, with its items and state.\n\n#### Signature\n\n```http\nGET /client/stowbo/bookings/{bookingId}/delegations (bookingId: string) -> Hand-offs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/delegations/{id}/resend":{"post":{"operationId":"StowboClientController_resendDelegation","summary":"Re-send a pickup pass","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Hand-off (`stowbo_pickup_delegation`) sk.","example":"66f1a2b3c4d5e6f708192a3d"}],"responses":{"201":{"description":"The hand-off with its new `passUrl`","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"Returned on create and resend only."}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/delegations/{id}/resend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/delegations/{id}/resend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Hand-off not found — No hand-off has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Hand-off not found","path":"/client/stowbo/delegations/{id}/resend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This hand-off is no longer active — Completed, revoked or expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This hand-off is no longer active","path":"/client/stowbo/delegations/{id}/resend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Issues a new pass link and sends it to the collector. The old link stops working.\n\n#### Signature\n\n```http\nPOST /client/stowbo/delegations/{id}/resend (id: string) -> The hand-off with its new `passUrl`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | HANDOFF_NOT_FOUND | Hand-off not found | No hand-off has that id. | — |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n| `409` | HANDOFF_NOT_ACTIVE | This hand-off is no longer active | Completed, revoked or expired. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/delegations/{id}":{"delete":{"operationId":"StowboClientController_revokeDelegation","summary":"Cancel a hand-off","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Hand-off (`stowbo_pickup_delegation`) sk.","example":"66f1a2b3c4d5e6f708192a3d"}],"responses":{"200":{"description":"The hand-off","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"Returned on create and resend only."}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/delegations/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/delegations/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Hand-off not found — No hand-off has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Hand-off not found","path":"/client/stowbo/delegations/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already collected — The items were already handed over.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already collected","path":"/client/stowbo/delegations/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Revokes the hand-off: the items go back to my own pickup code and the collector is told. Revoking one already revoked returns it unchanged.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/delegations/{id} (id: string) -> The hand-off\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | HANDOFF_NOT_FOUND | Hand-off not found | No hand-off has that id. | — |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n| `409` | ALREADY_COLLECTED | Already collected | The items were already handed over. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/my-pickups":{"get":{"operationId":"StowboClientController_myPickups","summary":"List passes addressed to me","description":"Hand-offs where the signed-in customer is the collector, matched by their email or phone — the same pass as the link, in the app. Active passes first. Never the owner's identity.\n\n#### Signature\n\n```http\nGET /client/stowbo/my-pickups () -> Passes (as `GET /client/stowbo/pickup/{token}`)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Passes (as `GET /client/stowbo/pickup/{token}`)","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/my-pickups","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"]}},"/client/stowbo/pickup/{token}":{"get":{"operationId":"StowboClientController_pickupPass","summary":"Get a pickup pass","description":"What the collector shows and where to go: the items, the place (address, access hours, instructions), what will be checked (`verify`; `bringId` when photo ID is needed), and — only while the pass is active — the code and QR payload the host scans. No sign-in; never the owner's identity.\n\n#### Signature\n\n```http\nGET /client/stowbo/pickup/{token} (token: string) -> The pass\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PASS_INVALID | This pass is not valid | The token matches no hand-off (or was replaced by a resend). | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"token","required":true,"in":"path","schema":{"type":"string"},"description":"The pass token from the pickup link.","example":"q3Zr9d0bVxK1pL7mN2sT5uW8yA4cE6gH"}],"responses":{"200":{"description":"The pass","content":{"application/json":{"schema":{"type":"object","properties":{"state":{"type":"string","enum":["active","completed","revoked","expired"]},"reference":{"type":"string"},"items":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","nullable":true},"identifier":{"type":"string","nullable":true},"unit":{"type":"string","nullable":true},"collected":{"type":"boolean"}}}},"place":{"type":"object","additionalProperties":true},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"bringId":{"type":"boolean"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"photo":{"type":"string","nullable":true},"collector":{"type":"string","nullable":true},"code":{"type":"string","nullable":true},"qr":{"type":"string","nullable":true,"example":"stowbo:pickup:K7Q2M9"},"completedAt":{"type":"string","nullable":true}}}}}},"404":{"description":"This pass is not valid — The token matches no hand-off (or was replaced by a resend).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"This pass is not valid","path":"/client/stowbo/pickup/{token}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"]}},"/client/stowbo/pickup/{token}/receipt":{"get":{"operationId":"StowboClientController_pickupReceipt","summary":"Get the receipt for a collected pickup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"token","required":true,"in":"path","schema":{"type":"string"},"description":"The pass token from the pickup link.","example":"q3Zr9d0bVxK1pL7mN2sT5uW8yA4cE6gH"}],"responses":{"200":{"description":"The receipt","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"collector":{"type":"string","nullable":true},"place":{"type":"object","additionalProperties":true},"completedAt":{"type":"string"},"items":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"at":{"type":"string","nullable":true},"by":{"type":"string","nullable":true},"verified":{"type":"array","items":{"type":"string"}},"photos":{"type":"array","items":{"type":"object","additionalProperties":true}},"collectedBy":{"type":"object","additionalProperties":true}}}}}}}}},"404":{"description":"This pass is not valid — Unknown token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"This pass is not valid","path":"/client/stowbo/pickup/{token}/receipt","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Nothing collected yet — The hand-off has not been completed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Nothing collected yet","path":"/client/stowbo/pickup/{token}/receipt","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"],"description":"After collection: who collected, where, when, and for each item when it was handed over, by whom, what was verified and the photos.\n\n#### Signature\n\n```http\nGET /client/stowbo/pickup/{token}/receipt (token: string) -> The receipt\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PASS_INVALID | This pass is not valid | Unknown token. | — |\n| `409` | NOT_COLLECTED | Nothing collected yet | The hand-off has not been completed. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/listings/{listingId}/availability":{"get":{"operationId":"StowboClientController_availability","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"},"example":"2026-09-05"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-12"},{"name":"quantity","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"addOns","required":false,"in":"query","schema":{"type":"string"},"description":"JSON array of chosen add-ons, for the quote.","example":"[{\"id\":\"ADD-3\",\"quantity\":1}]"},{"name":"discountCode","required":true,"in":"query","schema":{"type":"string"}},{"name":"customer","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Free count and price for the window","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/listings/{listingId}/availability","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"],"summary":"Check availability for a window","description":"How many of a listing are free for a window, and what it would cost. Pass `addOns` as a JSON array of chosen add-ons to have them priced in.\n\n#### Signature\n\n```http\nGET /client/stowbo/listings/{listingId}/availability (listingId: string, startDate?: string, endDate?: string, quantity?: integer, addOns?: string) -> Free count and price for the window\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/checkout/quote`"}},"/client/stowbo/listings/{listingId}/calendar":{"get":{"operationId":"StowboClientController_calendar","summary":"Get a place's availability calendar","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"ISO start date. Defaults to today.","example":"2026-09-01"},{"name":"days","required":false,"in":"query","schema":{"type":"integer"},"description":"How many days to return.","example":30}],"responses":{"200":{"description":"The calendar","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/listings/{listingId}/calendar","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"],"description":"A day-by-day availability grid, computed server-side and safe to show a guest — it exposes what is free, not who booked it.\n\n#### Signature\n\n```http\nGET /client/stowbo/listings/{listingId}/calendar (listingId: string, from?: string, days?: integer) -> The calendar\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/listings/{id}/calendar`"}},"/client/stowbo/checkout/quote":{"post":{"operationId":"StowboClientController_quote","summary":"Price a cart","description":"Server-computed breakdown for a whole cart. One call can mix a two-hour parking bay and a seven-day box, each priced on its own listing's unit and tax jurisdiction.\n\nDiscounts come off the goods **before** fees and tax and are allocated per line, so per-jurisdiction tax stays correct. Pass `discountCode` for a promo; auto-apply discounts are added on their own.\n\nNo side effects and nothing is thrown: a bad code and a line that is short on capacity both come back as reported problems, so the page can say which. `bookable` tells you whether the cart can proceed.\n\n#### Signature\n\n```http\nPOST /client/stowbo/checkout/quote (body) -> subtotal, serviceFee, protectionFee, tax, taxLines, total, bookable\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only — nothing is held or charged.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/checkout/hold`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"subtotal, serviceFee, protectionFee, tax, taxLines, total, bookable","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"subtotal":8400,"serviceFee":630,"protectionFee":200,"tax":742,"taxLines":[{"jurisdiction":"CA-SF","amount":742}],"total":9972,"bookable":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/checkout/quote","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"description":"The cart.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"One entry per thing being booked."},"discountCode":{"type":"string","example":"AUTUMN10"}}},"example":{"lines":[{"listingId":"LST-4821","startDate":"2026-09-05","endDate":"2026-09-12","quantity":1}],"discountCode":"AUTUMN10"}}}}}},"/client/stowbo/checkout/cart":{"post":{"operationId":"StowboClientController_saveCart","summary":"Save the cart","description":"Saves the cart and where the guest got to. **One open purchase per customer, updated in place** — closing the app loses nothing and reopening on another device picks up where they left off.\n\nSaving claims **no capacity**: recording progress must not take a bay off the market for everyone else. Pass `step`, `returnTo` and `context` to record the client's own position in the flow.\n\n#### Signature\n\n```http\nPOST /client/stowbo/checkout/cart (body) -> The saved checkout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Holds no capacity.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `409` | HOLD_IN_PROGRESS | A live hold is in progress — confirm or release it first | The customer already has a live hold. | Confirm it, or release it with `DELETE /client/stowbo/checkout/{checkoutId}`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/checkout/resume`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The saved checkout","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/checkout/cart","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"A live hold is in progress — confirm or release it first — The customer already has a live hold.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"A live hold is in progress — confirm or release it first","path":"/client/stowbo/checkout/cart","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"description":"The cart and the guest's position in the flow.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","additionalProperties":true}},"step":{"type":"string","example":"payment"},"returnTo":{"type":"string","example":"/checkout/payment"},"context":{"type":"object","additionalProperties":true}}},"example":{"lines":[{"listingId":"LST-4821","startDate":"2026-09-05","endDate":"2026-09-12","quantity":1}],"step":"payment"}}}}}},"/client/stowbo/checkout/resume":{"get":{"operationId":"StowboClientController_resume","summary":"Resume an unfinished purchase","description":"Returns the saved cart, **re-priced rather than replayed**: rates and tax may have moved, and a stale total is worse than no saved cart. `changed` says whether the price differs from what the guest last saw, so the UI can tell them.\n\nA lapsed hold is not an error — the cart survives even though the claim on the capacity does not.\n\n#### Signature\n\n```http\nGET /client/stowbo/checkout/resume () -> The re-priced cart, with a `changed` flag\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/checkout/{checkoutId}/progress`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The re-priced cart, with a `changed` flag","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/checkout/resume","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"]}},"/client/stowbo/checkout/{checkoutId}/progress":{"put":{"operationId":"StowboClientController_saveProgress","summary":"Record checkout progress","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"checkoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Checkout id.","example":"CHK-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/checkout/{checkoutId}/progress","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your checkout — The checkout belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your checkout","path":"/client/stowbo/checkout/{checkoutId}/progress","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Checkout not found — No checkout has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Checkout not found","path":"/client/stowbo/checkout/{checkoutId}/progress","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Stamps the step the guest reached **without re-pricing** — for cheap, frequent position updates as they move through the flow.\n\n#### Signature\n\n```http\nPUT /client/stowbo/checkout/{checkoutId}/progress (checkoutId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | CHECKOUT_NOT_FOUND | Checkout not found | No checkout has that id. | Resume to get the current one. |\n| `403` | NOT_YOUR_CHECKOUT | Not your checkout | The checkout belongs to another customer. | Check the id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/checkout/cart`","requestBody":{"description":"The step.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"step":{"type":"string","example":"payment"},"returnTo":{"type":"string"},"context":{"type":"object","additionalProperties":true}}},"example":{"step":"payment"}}}}}},"/client/stowbo/checkout/suggestions":{"post":{"operationId":"StowboClientController_suggestions","summary":"Get mid-booking suggestions","description":"Two lists derived from the cart's own listings, dates and location: `alternatives` (other places of the same kind nearby — so the comparison the guest was about to go and make happens here instead) and `complements` (a different kind nearby, for the one-trip bundle).\n\nBoth are availability-checked and priced for the cart's window, so adding one is a single click. Ordered by relevance.\n\n#### Signature\n\n```http\nPOST /client/stowbo/checkout/suggestions (body) -> `alternatives` and `complements`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/checkout/quote`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`alternatives` and `complements`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"409":{"description":"Something in the cart went unavailable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"],"requestBody":{"description":"The cart.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"lines":[{"listingId":"LST-4821","startDate":"2026-09-05","endDate":"2026-09-12","quantity":1}]}}}}}},"/client/stowbo/checkout/hold":{"post":{"operationId":"StowboClientController_startCheckout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The hold, with `holdId` and `expiresAt`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"holdId":"HLD-4821","checkoutId":"CHK-4821","expiresAt":"2026-08-30T12:07:00.000Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/checkout/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Something in the cart went unavailable — A line can no longer be satisfied.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Something in the cart went unavailable","path":"/client/stowbo/checkout/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"summary":"Start checkout and hold the capacity","description":"Takes the capacity off the market for a few minutes while the guest pays, so two people cannot both buy the last bay. Returns `holdId` and `expiresAt`.\n\n**Nothing is charged here.** The hold expires on its own if the guest walks away — no cleanup call is required, though releasing early frees the space for someone else sooner.\n\n#### Signature\n\n```http\nPOST /client/stowbo/checkout/hold (body) -> The hold, with `holdId` and `expiresAt`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Holds capacity but charges nothing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `409` | CART_UNAVAILABLE | Something in the cart went unavailable | A line can no longer be satisfied. | Re-quote and let the guest adjust. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/checkout/{checkoutId}/confirm`","requestBody":{"description":"The cart to hold.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"lines":[{"listingId":"LST-4821","startDate":"2026-09-05","endDate":"2026-09-12","quantity":1}]}}}}}},"/client/stowbo/checkout/{checkoutId}/confirm":{"post":{"operationId":"StowboClientController_confirmCheckout","summary":"Pay and confirm","description":"Turns a hold into a booking and takes the money. **Captures by default** — the capacity is withheld from the moment it is confirmed, so the money should not be left contingent. Pass `captureNow: false` to authorise only.\n\nA declined card leaves the hold intact until it expires rather than confirming an unpaid booking, so the guest can retry with another card without losing the space.\n\n#### Signature\n\n```http\nPOST /client/stowbo/checkout/{checkoutId}/confirm (checkoutId: string, body) -> The booking\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Charges the guest.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `402` | PAYMENT_FAILED | Payment failed — the hold is still live | The card was declined. | Retry with another method before the hold expires. |\n| `409` | HOLD_EXPIRED_OR_CONFIRMED | Hold expired or already confirmed | The hold lapsed, or the checkout is already a booking. | Start a new hold. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/stowbo/checkout/{checkoutId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"checkoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Checkout id.","example":"CHK-4821"}],"responses":{"201":{"description":"The booking","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/checkout/{checkoutId}/confirm","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"402":{"description":"Payment failed — the hold is still live — The card was declined.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"Payment failed — the hold is still live","path":"/client/stowbo/checkout/{checkoutId}/confirm","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Hold expired or already confirmed — The hold lapsed, or the checkout is already a booking.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Hold expired or already confirmed","path":"/client/stowbo/checkout/{checkoutId}/confirm","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"description":"The payment.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentMethodId":{"type":"string","description":"Web card.","example":"pm_1Abc123"},"paymentIntentId":{"type":"string","description":"A confirmed intent, for native or wallet payments.","example":"pi_3Abc123"},"captureNow":{"type":"boolean","description":"Default true.","example":true}}},"example":{"paymentMethodId":"pm_1Abc123"}}}}}},"/client/stowbo/checkout/{checkoutId}":{"delete":{"operationId":"StowboClientController_abandonCheckout","summary":"Abandon checkout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"checkoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Checkout id.","example":"CHK-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/checkout/{checkoutId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your checkout — The checkout belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your checkout","path":"/client/stowbo/checkout/{checkoutId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Checkout not found — No checkout has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Checkout not found","path":"/client/stowbo/checkout/{checkoutId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Releases the held capacity and abandons the checkout. Optional — a hold expires by itself — but calling it returns the space to the market immediately.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/checkout/{checkoutId} (checkoutId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | CHECKOUT_NOT_FOUND | Checkout not found | No checkout has that id. | Nothing to release. |\n| `403` | NOT_YOUR_CHECKOUT | Not your checkout | The checkout belongs to someone else. | Check the id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/checkout/hold`"}},"/client/stowbo/bookings":{"post":{"operationId":"StowboClientController_book","summary":"Book directly","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The booking.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"lines":[{"listingId":"LST-4821","startDate":"2026-09-05","endDate":"2026-09-12","quantity":1}]}}}},"responses":{"201":{"description":"The booking","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"At least one line is required — The booking has no lines.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"At least one line is required","path":"/client/stowbo/bookings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Creates a booking without the hold-and-confirm flow — one order with a line per thing booked. Because it skips the hold, the capacity is only claimed at the moment of the call; prefer the checkout flow for anything a guest pays for interactively.\n\n#### Signature\n\n```http\nPOST /client/stowbo/bookings (body) -> The booking\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `400` | LINE_REQUIRED | At least one line is required | The booking has no lines. | Include what is being booked. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/checkout/hold`"},"get":{"operationId":"StowboClientController_myBookings","summary":"List my bookings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","description":"Default 100, max 500.","schema":{"type":"integer"},"example":100}],"responses":{"200":{"description":"Bookings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"The caller's bookings.\n\n#### Signature\n\n```http\nGET /client/stowbo/bookings (status?: string, page?: integer, pageSize?: integer) -> Bookings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/bookings/{bookingId}`"}},"/client/stowbo/bookings/{bookingId}":{"get":{"operationId":"StowboClientController_myBooking","summary":"Get one of my bookings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"The booking with its codes","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"A booking with its access codes — what the guest shows or enters at the space.\n\n#### Signature\n\n```http\nGET /client/stowbo/bookings/{bookingId} (bookingId: string) -> The booking with its codes\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Contains access codes — treat as sensitive.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/bookings/{bookingId}/host`"}},"/client/stowbo/bookings/{bookingId}/host":{"get":{"operationId":"StowboClientController_bookingHost","summary":"Get my booking's host","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"The host contact card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/host","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/host","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/host","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"The host's contact card — name, email, phone — for a booking the caller made. Scoped to their own bookings, since it exposes a real person's contact details.\n\n#### Signature\n\n```http\nGET /client/stowbo/bookings/{bookingId}/host (bookingId: string) -> The host contact card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Personal contact details.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/bookings/{bookingId}/customer`"}},"/client/stowbo/bookings/{bookingId}/request":{"post":{"operationId":"StowboClientController_requestBookingAction","summary":"Ask the host to act","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Raises a request against a booking — bring my things out, put them back, check me out. The host sees it and performs the move; the guest watches its state.\n\n#### Signature\n\n```http\nPOST /client/stowbo/bookings/{bookingId}/request (bookingId: string, body) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/items/{itemId}/request`","requestBody":{"description":"What is being asked.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"type":"retrieval","note":"Arriving around 3pm"}}}}}},"/client/stowbo/my-items":{"get":{"operationId":"StowboClientController_myItems","summary":"List everything I have booked","description":"One entry per thing booked, from the **same records the host works from** — so guest and host cannot disagree about where something is.\n\n`present` is the direction of the last movement, not whether the booking is over: being out at 2pm is lunch, not finished. Pass `active=true` for only what is still running.\n\n#### Signature\n\n```http\nGET /client/stowbo/my-items (active?: boolean, booking?: string, page?: integer, pageSize?: integer) -> Booking items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/my-stuff`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"active","required":false,"in":"query","schema":{"type":"boolean"},"description":"Only what is still running.","example":true},{"name":"booking","required":false,"in":"query","description":"Scope to one booking.","schema":{"type":"string"},"example":"BKG-4821"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Booking items","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/my-items","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"]}},"/client/stowbo/my-stuff":{"get":{"operationId":"StowboClientController_myStuff","summary":"List what I have stored right now","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Stored items","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/my-stuff","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Only the things currently present at a space — the narrower \"what have I actually left somewhere\" view.\n\n#### Signature\n\n```http\nGET /client/stowbo/my-stuff () -> Stored items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/my-items`"}},"/client/stowbo/park":{"post":{"operationId":"StowboClientController_startParking","summary":"Start a parking session","description":"For a self-serve lot with no attendant: the guest scans the zone QR or follows an SMS link, taps start, and the meter runs. Opens an OPEN stay, already checked in.\n\n**Self-serve spaces only** — an attended space is checked in by the host.\n\n#### Signature\n\n```http\nPOST /client/stowbo/park (body) -> The running session\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `400` | SPACE_ATTENDED | This space is attended — the host checks you in. | The listing is not self-serve. | The host will check you in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/park/{itemId}/stop`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The running session","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This space is attended — the host checks you in. — The listing is not self-serve.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This space is attended — the host checks you in.","path":"/client/stowbo/park","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/park","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"description":"Where they are parking.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"listingId":"LST-4821","plate":"7ABC123"}}}}}},"/client/stowbo/park/{itemId}":{"get":{"operationId":"StowboClientController_parkingStatement","summary":"Get my running parking session","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"200":{"description":"The running statement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/park/{itemId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your session — The session belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your session","path":"/client/stowbo/park/{itemId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/park/{itemId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"What the session has cost so far. Read-only, and the figure keeps moving while the meter runs.\n\n#### Signature\n\n```http\nGET /client/stowbo/park/{itemId} (itemId: string) -> The running statement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `403` | NOT_YOUR_SESSION | Not your session | The session belongs to someone else. | Check the item id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/park/{itemId}/stop`"}},"/client/stowbo/park/{itemId}/stop":{"post":{"operationId":"StowboClientController_stopParking","summary":"Stop my parking session","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The closed session and its charge","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/park/{itemId}/stop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/park/{itemId}/stop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"That session is already closed — It was already stopped.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"That session is already closed","path":"/client/stowbo/park/{itemId}/stop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Closes the session and bills the time used. This is what ends the meter — leaving without calling it keeps the charge accruing.\n\n#### Signature\n\n```http\nPOST /client/stowbo/park/{itemId}/stop (itemId: string) -> The closed session and its charge\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Bills the guest for the time used.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `409` | SESSION_CLOSED | That session is already closed | It was already stopped. | Read the statement instead. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/park/{itemId}`"}},"/client/stowbo/items/{itemId}/movement":{"post":{"operationId":"StowboClientController_selfMovement","summary":"Record my own movement (self-serve)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The movement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"direction must be 'in' or 'out' — `direction` is missing or something else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"direction must be 'in' or 'out'","path":"/client/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking item — The item belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking item","path":"/client/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"That's already here / That's already out — The movement repeats the last direction.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"That's already here / That's already out","path":"/client/stowbo/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"The guest records their own in or out — taking tools out, putting them back. **Unmanned spaces only**; attended spaces stay host-driven, and calling this on one is refused.\n\nA movement is not a check-out: the space stays held and nothing is billed.\n\n#### Signature\n\n```http\nPOST /client/stowbo/items/{itemId}/movement (itemId: string, body) -> The movement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Never billable and never touches capacity.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `403` | NOT_YOUR_ITEM | Not your booking item | The item belongs to another customer. | Check the item id. |\n| `400` | DIRECTION_INVALID | direction must be 'in' or 'out' | `direction` is missing or something else. | Send `in` or `out`. |\n| `409` | ALREADY_THERE | That's already here / That's already out | The movement repeats the last direction. | Check the current state first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/items/{itemId}/request`","requestBody":{"description":"The movement.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["direction"],"properties":{"direction":{"type":"string","enum":["in","out"],"example":"out"},"note":{"type":"string"}}},"example":{"direction":"out"}}}}}},"/client/stowbo/items/{itemId}/request":{"post":{"operationId":"StowboClientController_requestMovement","summary":"Request a retrieval or return (attended)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This space is self-serve — move it yourself, no request needed. — The listing is unmanned.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This space is self-serve — move it yourself, no request needed.","path":"/client/stowbo/items/{itemId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/items/{itemId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking item — The item belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking item","path":"/client/stowbo/items/{itemId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/items/{itemId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"A request is already open on this item — An earlier request has not been closed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"A request is already open on this item","path":"/client/stowbo/items/{itemId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Raises a **tracked** request the guest can watch through `requested → preparing → ready → fulfilled`. The host performs the actual move.\n\nFor attended spaces; on a self-serve space the guest moves things themselves and the request is refused. One open request per item at a time.\n\n#### Signature\n\n```http\nPOST /client/stowbo/items/{itemId}/request (itemId: string, body) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `403` | NOT_YOUR_ITEM | Not your booking item | The item belongs to another customer. | Check the item id. |\n| `400` | SPACE_SELF_SERVE | This space is self-serve — move it yourself, no request needed. | The listing is unmanned. | Use `POST /client/stowbo/items/{itemId}/movement`. |\n| `409` | REQUEST_OPEN | A request is already open on this item | An earlier request has not been closed. | Cancel it first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /client/stowbo/items/{itemId}/request`","requestBody":{"description":"What is being asked.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"type":"retrieval","note":"Arriving around 3pm"}}}}},"delete":{"operationId":"StowboClientController_cancelRequest","summary":"Cancel my open request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"No open request on this item — Nothing is outstanding.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No open request on this item","path":"/client/stowbo/items/{itemId}/request","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/items/{itemId}/request","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking item — The item belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking item","path":"/client/stowbo/items/{itemId}/request","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/items/{itemId}/request","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Withdraws an open retrieval or return request. Worth doing if plans change — the host may already be preparing it.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/items/{itemId}/request (itemId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `403` | NOT_YOUR_ITEM | Not your booking item | The item belongs to another customer. | Check the item id. |\n| `400` | NO_OPEN_REQUEST | No open request on this item | Nothing is outstanding. | Nothing to cancel. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/items/{itemId}/request`"}},"/client/stowbo/host/items/{itemId}/request":{"post":{"operationId":"StowboClientController_hostUpdateRequest","summary":"Advance a guest request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/items/{itemId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/host/items/{itemId}/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Moves a guest's request forward — `acknowledged`, `preparing`, `ready`. **Each step notifies the guest**, so the states are worth using honestly rather than jumping straight to ready.\n\nThe actual hand-out is a movement, not this call.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/items/{itemId}/request (itemId: string, body) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Notifies the guest at each step.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/items/{itemId}/movement`","requestBody":{"description":"The new state.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["acknowledged","preparing","ready"],"example":"preparing"},"note":{"type":"string"}}},"example":{"status":"preparing"}}}}}},"/client/stowbo/bookings/{bookingId}/due":{"get":{"operationId":"StowboClientController_myBookingDue","summary":"Get what I owe or get back","description":"The one money question for the state I am about to enter: `action=current` (now, including any running meter) or `action=cancel` (if I cancel now — the fee and refund the cancel itself will use). Ask before cancelling.\n\n#### Signature\n\n```http\nGET /client/stowbo/bookings/{bookingId}/due (bookingId: string, action?: string) -> The payment summary plus the answer for that action\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/bookings/{bookingId}/cancel`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"},{"name":"action","required":false,"in":"query","schema":{"type":"string","enum":["current","cancel"],"default":"current"}}],"responses":{"200":{"description":"The payment summary plus the answer for that action","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"action":{"type":"string"},"total":{"type":"number"},"paid":{"type":"number"},"balance":{"type":"number"},"currency":{"type":"string"},"accrued":{"type":"number","description":"Not-yet-billed meter on items still present."},"metered":{"type":"boolean"},"pastWindow":{"type":"boolean"},"lines":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Fee lines the action would add (cancellation / no-show fees)."},"feeTotal":{"type":"number"},"due":{"type":"number"},"refund":{"type":"number"},"payState":{"type":"string"},"message":{"type":"string"}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/due","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/due","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/due","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"]}},"/client/stowbo/bookings/{bookingId}/cancel":{"put":{"operationId":"StowboClientController_cancel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"The cancelled booking","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This is already checked in — check it out instead of cancelling — An item on the booking has been checked in.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This is already checked in — check it out instead of cancelling","path":"/client/stowbo/bookings/{bookingId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"summary":"Cancel a booking","description":"Cancels a booking — **only before anything has been handed over**. Once an item is checked in, the way out is to check it out, not to cancel, because the space has been used and the bill reflects that.\n\nThe refund and any cancellation fee follow the booking's cancellation policy — the same numbers `GET /client/stowbo/bookings/{bookingId}/due?action=cancel` shows. Held units are released and any uncaptured authorisation is voided.\n\n#### Signature\n\n```http\nPUT /client/stowbo/bookings/{bookingId}/cancel (bookingId: string, body) -> The cancelled booking\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n| `400` | ALREADY_CHECKED_IN | This is already checked in — check it out instead of cancelling | An item on the booking has been checked in. | Check it out. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/bookings/{bookingId}/due`\n- `POST /client/stowbo/host/items/{itemId}/checkout`","requestBody":{"description":"Optional reason. Defaults to \"Cancelled by guest\".","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Plans changed"}}}}}},"/client/stowbo/bookings/{bookingId}/extend":{"post":{"operationId":"StowboClientController_extend","summary":"Extend a stay","description":"Moves the end of a stay later. Re-prices the space line over its new window, **refuses if the space is not free** for the added time (a later booking blocks it), and takes payment for the extra time **before** committing — pass `paymentMethodId` or a confirmed `paymentIntentId`.\n\nA decline changes nothing: the booking keeps its original window.\n\n#### Signature\n\n```http\nPOST /client/stowbo/bookings/{bookingId}/extend (bookingId: string, body) -> The extended line, what was added, and the booking's new total and balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Charges before committing; a decline is a no-op.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n| `400` | NOT_A_SPACE_LINE | Only a space line can be extended | The line is an add-on, not a space. | Extend the space line. |\n| `409` | UNAVAILABLE | Cannot extend — the space is not free for that time (N of M available). | A later booking or a closure blocks the added time. The body carries `available` and, for a closure, `until: \"blackout\"`. | Try a shorter extension. |\n| `402` | PAYMENT_REQUIRED | Extending adds <amount> <currency> — payment required. | The extra time costs more and no payment was sent. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/bookings/{bookingId}/addons`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The extended line, what was added, and the booking's new total and balance","content":{"application/json":{"schema":{"type":"object","properties":{"line":{"type":"number"},"endDate":{"type":"string"},"added":{"type":"number"},"paid":{"type":"boolean"},"total":{"type":"number"},"balance":{"type":"number"}}}}}},"400":{"description":"Only a space line can be extended — The line is an add-on, not a space.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a space line can be extended","path":"/client/stowbo/bookings/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"402":{"description":"Extending adds <amount> <currency> — payment required. — The extra time costs more and no payment was sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"Extending adds <amount> <currency> — payment required.","path":"/client/stowbo/bookings/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Cannot extend — the space is not free for that time (N of M available). — A later booking or a closure blocks the added time. The body carries `available` and, for a closure, `until: \"blackout\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Cannot extend — the space is not free for that time (N of M available).","path":"/client/stowbo/bookings/{bookingId}/extend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"description":"The new end and the payment.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"endDate":{"type":"string","example":"2026-09-15"},"paymentMethodId":{"type":"string","example":"pm_1Abc123"},"paymentIntentId":{"type":"string","example":"pi_3Abc123"}}},"example":{"endDate":"2026-09-15","paymentMethodId":"pm_1Abc123"}}}}}},"/client/stowbo/bookings/{bookingId}/addons":{"post":{"operationId":"StowboClientController_addAddon","summary":"Add an add-on to a booking","description":"Adds a priced add-on line — insurance, cleaning, an EV charge. Payment is taken **before** the add-on is attached; a decline adds nothing.\n\n#### Signature\n\n```http\nPOST /client/stowbo/bookings/{bookingId}/addons (bookingId: string, body) -> The updated booking\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Charges before attaching.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/bookings/{bookingId}/settle-payment`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The updated booking","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/addons","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/addons","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/addons","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"description":"The add-on and the payment.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"addonId":{"type":"string","example":"ADD-3"},"quantity":{"type":"integer","example":1},"paymentMethodId":{"type":"string","example":"pm_1Abc123"},"paymentIntentId":{"type":"string"}}},"example":{"addonId":"ADD-3","quantity":1,"paymentMethodId":"pm_1Abc123"}}}}}},"/client/stowbo/bookings/{bookingId}/settle-payment":{"post":{"operationId":"StowboClientController_settlePayment","summary":"Settle an outstanding balance","description":"Settles what is owed on a booking after an extension, an add-on, a damage fee, or a metered or deposit-first stay closing out. The app raises a Stripe sheet for the balance and hands the confirmed PaymentIntent here.\n\n**Idempotent per intent** — replaying the same intent id settles once.\n\n#### Signature\n\n```http\nPOST /client/stowbo/bookings/{bookingId}/settle-payment (bookingId: string, body) -> The settlement result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Idempotent per payment intent.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n| `400` | NOTHING_TO_PAY | Nothing to pay | The booking has no outstanding balance. | Read the booking first. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/pay/{bookingId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The settlement result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Nothing to pay — The booking has no outstanding balance.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Nothing to pay","path":"/client/stowbo/bookings/{bookingId}/settle-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/settle-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/settle-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/settle-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"requestBody":{"description":"The confirmed intent.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentIntentId"],"properties":{"paymentIntentId":{"type":"string","example":"pi_3Abc123"}}},"example":{"paymentIntentId":"pi_3Abc123"}}}}}},"/client/stowbo/host/take-payment":{"post":{"operationId":"StowboClientController_takePayment","summary":"Record an in-person card payment","description":"Records a Tap to Pay / terminal payment a host collected in person — a booking balance, an add-on, a tip, or an ad-hoc walk-up amount.\n\nThe app does the collection: create a `card_present` PaymentIntent via `POST /storefront/stripe/terminal/intent`, collect on the phone, confirm, then hand the **succeeded** intent id here to record it against the platform Stripe account and credit the host. This endpoint records; it does not charge.\n\n`reference` is free text for a walk-up with no booking — a plate, a name, an invoice number.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/take-payment (body) -> The recorded payment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/payment-request`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The recorded payment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/take-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"The collected payment.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentIntentId":{"type":"string","description":"The succeeded card_present intent.","example":"pi_3Abc123"},"bookingId":{"type":"string","example":"BKG-4821"},"amount":{"type":"number","example":2500},"category":{"type":"string","enum":["booking","addon","tip","adhoc","other"],"example":"booking"},"reference":{"type":"string","description":"Plate, name or invoice — for walk-ups with no booking.","example":"7ABC123"},"description":{"type":"string"},"email":{"type":"string","description":"For a receipt.","example":"ada@example.com"},"phone":{"type":"string","description":"For a receipt."}}},"example":{"paymentIntentId":"pi_3Abc123","bookingId":"BKG-4821","amount":2500,"category":"booking"}}}}}},"/client/stowbo/host/payment-request":{"post":{"operationId":"StowboClientController_createPaymentRequest","summary":"Create a self-serve payment request","description":"When the customer is not tapping a card at the host, this generates a pay link and a QR code for the same URL. Records a **pending** ledger row and returns `{ url, paymentRequestId }`; the customer pays on the signed-out web `/pay` page, which settles it.\n\nSet `send: true` with `email` or `phone` to deliver the link immediately — that sends a real message to the customer.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/payment-request (body) -> `url` and `paymentRequestId`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `send: true` messages the customer.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/payment-request/{requestId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`url` and `paymentRequestId`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"url":"https://stowbo.example.com/pay/BKG-4821","paymentRequestId":"PR-4821"}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/payment-request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"What is being asked for, and whether to send it.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bookingId":{"type":"string","example":"BKG-4821"},"amount":{"type":"number","example":2500},"category":{"type":"string","enum":["booking","addon","tip","adhoc","other"],"example":"booking"},"reference":{"type":"string","example":"7ABC123"},"description":{"type":"string"},"send":{"type":"boolean","description":"Deliver the link now.","example":true},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"example":{"bookingId":"BKG-4821","category":"booking","send":true,"phone":"+15551234567"}}}}}},"/client/stowbo/host/payment-request/{requestId}/complete":{"post":{"operationId":"StowboClientController_completePaymentRequest","summary":"Mark a payment request paid","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Payment request id.","example":"PR-4821"}],"responses":{"201":{"description":"The settled request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/payment-request/{requestId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Settles a pending payment request. Called by the web `/pay` flow once the customer has paid — marking one paid by hand records money that may not have arrived.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/payment-request/{requestId}/complete (requestId: string, body) -> The settled request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Driven by the pay flow, not by hand.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/payment-request/{requestId}`","requestBody":{"description":"The payment confirmation.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"paymentIntentId":"pi_3Abc123"}}}}}},"/client/stowbo/host/payment-request/{requestId}":{"get":{"operationId":"StowboClientController_paymentRequestStatus","summary":"Get a payment request's status","description":"The live status of a pending payment request — it flips to `paid` the moment the customer completes it. For a host watching their screen after sending a link or showing a QR.\n\nA socket `payment-completed` push is also emitted; this polling route is the fallback.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/payment-request/{requestId} (requestId: string) -> The request status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/payment-request/{requestId}/complete`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Payment request id.","example":"PR-4821"}],"responses":{"200":{"description":"The request status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/payment-request/{requestId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/transactions":{"get":{"operationId":"StowboClientController_hostTransactions","summary":"List my transactions","description":"The host's collected and pending payments, newest first — a server-scoped query over the `sf_transaction` ledger for stowbo rows this host created or collected.\n\nPending rows carry their pay `url`, so a host who walked away from a link or QR can come back, re-show the QR or resend it.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/transactions () -> Transactions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/earnings`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Transactions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/transactions","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/transactions/{ref}/refund":{"post":{"operationId":"StowboClientController_refundTransaction","summary":"Refund a payment I collected","description":"Refunds a paid transaction the signed-in host collected, in full (omit `amount`) or in part. Writes a separate `refund` ledger row and marks the original refunded or partially refunded; its amount is never changed.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/transactions/{ref}/refund (ref: string, body) -> The refund\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n| `400` | REF_REQUIRED | A transaction reference is required | Empty ref. | — |\n| `404` | TRANSACTION_NOT_FOUND | Transaction <ref> not found | Unknown ref. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","description":"The gateway reference of the paid transaction.","schema":{"type":"string"},"example":"pi_3Abc123"}],"responses":{"201":{"description":"The refund","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"refund":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"refundedTotal":{"type":"number"},"fullyRefunded":{"type":"boolean"},"remaining":{"type":"number"}}}}}},"400":{"description":"A transaction reference is required — Empty ref.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A transaction reference is required","path":"/client/stowbo/host/transactions/{ref}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/transactions/{ref}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/transactions/{ref}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Transaction <ref> not found — Unknown ref.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction <ref> not found","path":"/client/stowbo/host/transactions/{ref}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number"},"reason":{"type":"string","description":"Up to 240 characters."}}},"example":{"amount":10,"reason":"Late handover"}}}}}},"/client/stowbo/host/transactions/{ref}/receipt":{"post":{"operationId":"StowboClientController_sendReceipt","summary":"Resend a receipt","description":"Emails and/or texts the receipt for a payment the host collected. With no email or phone it goes to the linked booking's customer.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/transactions/{ref}/receipt (ref: string, body) -> { ok, sentTo }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n| `400` | REF_REQUIRED | A transaction reference is required | Empty ref. | — |\n| `404` | TRANSACTION_NOT_FOUND | Transaction <ref> not found | Unknown ref. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","description":"The gateway reference of the paid transaction.","schema":{"type":"string"},"example":"pi_3Abc123"}],"responses":{"201":{"description":"{ ok, sentTo }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A transaction reference is required — Empty ref.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A transaction reference is required","path":"/client/stowbo/host/transactions/{ref}/receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/transactions/{ref}/receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/transactions/{ref}/receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Transaction <ref> not found — Unknown ref.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction <ref> not found","path":"/client/stowbo/host/transactions/{ref}/receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"phone":{"type":"string"},"bookingId":{"type":"string"}}}}}}}},"/client/stowbo/host/payment-request/{requestId}/cancel":{"post":{"operationId":"StowboClientController_cancelPaymentRequest","summary":"Void a payment request","description":"Voids a still-unpaid request so its link and QR stop working. Cancelling one already cancelled returns `alreadyCancelled`. A paid request is refused — refund it instead.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/payment-request/{requestId}/cancel (requestId: string) -> { ok, cancelled } or { ok, alreadyCancelled }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n| `404` | REQUEST_NOT_FOUND | Payment request <id> not found | Unknown id. | — |\n| `400` | ALREADY_PAID | That payment is already paid — refund it instead of cancelling. | Paid. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Payment request id.","example":"66f1a2b3c4d5e6f708192a3e"}],"responses":{"201":{"description":"{ ok, cancelled } or { ok, alreadyCancelled }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"That payment is already paid — refund it instead of cancelling. — Paid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"That payment is already paid — refund it instead of cancelling.","path":"/client/stowbo/host/payment-request/{requestId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/payment-request/{requestId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/payment-request/{requestId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Payment request <id> not found — Unknown id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payment request <id> not found","path":"/client/stowbo/host/payment-request/{requestId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/wallet":{"get":{"operationId":"StowboClientController_hostWallet","summary":"Get my wallet","description":"The host's earnings wallet, derived from the ledger: earned (net of refunds), pending payment links, refunded, `available` to pay out, the payout minimum (the platform floor or my own higher one), paid out, payouts in progress, and the balance still expected from live bookings.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/wallet () -> The wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The wallet","content":{"application/json":{"schema":{"type":"object","properties":{"wallet":{"type":"string"},"currency":{"type":"string"},"status":{"type":"string"},"earned":{"type":"number"},"pending":{"type":"number"},"refunded":{"type":"number"},"balance":{"type":"number"},"available":{"type":"number"},"held":{"type":"number"},"reserved":{"type":"number"},"minPayout":{"type":"number"},"paidOut":{"type":"number"},"pendingPayout":{"type":"number"},"expectedFromBookings":{"type":"number"},"pendingBookings":{"type":"number"}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/wallet","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/wallet","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/payouts":{"get":{"operationId":"StowboClientController_hostPayouts","summary":"List my payouts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Payouts","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/payouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/payouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"The host's payout history: number, amount, status, method and destination.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/payouts () -> Payouts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n\nPlus the standard platform errors: `429`, `500`."},"post":{"operationId":"StowboClientController_requestHostPayout","summary":"Request a payout","description":"Pays out the available balance, or `amount` of it, to `methodId` (default destination if omitted). The minimum is enforced on the server.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/payouts (body) -> { ok, payout, amount, status }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n| `400` | NOTHING_AVAILABLE | Nothing available to pay out. | Available balance is zero. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ ok, payout, amount, status }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Nothing available to pay out. — Available balance is zero.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Nothing available to pay out.","path":"/client/stowbo/host/payouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/payouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/payouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Defaults to everything available."},"methodId":{"type":"string"},"notes":{"type":"string"}}},"example":{"amount":150}}}}}},"/client/stowbo/host/payout-methods":{"get":{"operationId":"StowboClientController_hostPayoutMethods","summary":"List my payout destinations","description":"Saved bank accounts and PayPal addresses on the host's wallet. Account and routing numbers are never returned — only the last four.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/payout-methods () -> Payout methods\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Payout methods","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/payout-methods","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/payout-methods","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]},"post":{"operationId":"StowboClientController_addHostPayoutMethod","summary":"Add a payout destination","description":"Adds a bank account or PayPal address. Bank numbers are encrypted on arrival and only the last four are returned.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/payout-methods (body) -> The method (masked)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The method (masked)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/payout-methods","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/payout-methods","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["bank","paypal"]},"label":{"type":"string"},"isDefault":{"type":"boolean"},"bank":{"type":"object","properties":{"bankName":{"type":"string"},"accountType":{"type":"string"},"routingNumber":{"type":"string"},"accountNumber":{"type":"string"},"accountHolderName":{"type":"string"}}},"paypal":{"type":"object","properties":{"email":{"type":"string"}}}}},"example":{"type":"paypal","paypal":{"email":"host@example.com"},"isDefault":true}}}}}},"/client/stowbo/host/payout-methods/{methodId}/default":{"put":{"operationId":"StowboClientController_setHostDefaultPayoutMethod","summary":"Make a payout destination the default","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id."}],"responses":{"200":{"description":"All payout methods","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/payout-methods/{methodId}/default","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/payout-methods/{methodId}/default","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"#### Signature\n\n```http\nPUT /client/stowbo/host/payout-methods/{methodId}/default (methodId: string) -> All payout methods\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/host/payout-methods/{methodId}":{"delete":{"operationId":"StowboClientController_removeHostPayoutMethod","summary":"Remove a payout destination","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id."}],"responses":{"200":{"description":"The remaining payout methods","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/payout-methods/{methodId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/payout-methods/{methodId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"#### Signature\n\n```http\nDELETE /client/stowbo/host/payout-methods/{methodId} (methodId: string) -> The remaining payout methods\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/host/apply":{"post":{"operationId":"StowboClientController_applyAsHost","summary":"Apply to list space","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"requestBody":{"description":"The application.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"formData":{"type":"object","description":"Validated against the benefit's application form collection.","additionalProperties":true}}},"example":{"formData":{"businessName":"Acme Storage","city":"San Francisco"}}}}},"responses":{"201":{"description":"The application","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/apply","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"Enrols the caller in the `stowbo-host` CRM benefit. `formData` is validated against the benefit's own application form collection, so the fields required depend on how the benefit is configured rather than being fixed here.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/apply (body) -> The application\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/me`"}},"/client/stowbo/host/me":{"get":{"operationId":"StowboClientController_hostMe","summary":"Get my host application","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The application","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/me","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"The caller's host application and where it stands.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/me () -> The application\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/apply`"}},"/client/stowbo/host/listings":{"get":{"operationId":"StowboClientController_myListings","summary":"List my listings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Listings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"The host's own listings.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/listings () -> Listings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/listings/{id}`"},"post":{"operationId":"StowboClientController_createListing","summary":"Create a listing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The listing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Lists a space. A new listing is not live until its status is set — create it, add units, then publish.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/listings (body) -> The listing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/host/listings/{id}/status`","requestBody":{"description":"The listing.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Mission garage bay","spaceType":"parking","city":"San Francisco"}}}}}},"/client/stowbo/pay/{ref}":{"get":{"operationId":"StowboClientController_paymentView","summary":"Get what a payment link is for","description":"Backs the signed-out take-payment page. `ref` is a payment **reference**, resolved in order as an ad-hoc payment request's pay token, then a ledger row `sk`, then a booking id — one endpoint serves link/QR requests and booking balances alike. The server derives the amount due; a link's amount is display-only and never trusted.\n\nDeliberately thin: what the charge is for and the balance. **No guest identity, no access codes** — anyone with the reference can load it.\n\n#### Signature\n\n```http\nGET /client/stowbo/pay/{ref} (ref: string) -> What is owed, and what for\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Public and deliberately minimal — no identity or codes.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <ref> not found | The reference matches no payment request, ledger row or booking. | List your bookings. |\n| `400` | REF_REQUIRED | ref is required | Empty reference. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/pay/{ref}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","description":"Payment reference: a payment request pay token, a ledger row `sk`, or a booking id.","schema":{"type":"string"},"example":"stowbo-pr-7Kq2M9"}],"responses":{"200":{"description":"What is owed, and what for","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"type":"string","description":"Present for a booking balance."},"request":{"type":"string","description":"Present for an ad-hoc payment request."},"reference":{"type":"string","nullable":true},"venue":{"type":"string","nullable":true},"currency":{"type":"string"},"total":{"type":"number"},"paid":{"type":"number"},"balance":{"type":"number"},"status":{"type":"string"},"cancelled":{"type":"boolean"},"category":{"type":"string","description":"Payment requests only."},"lines":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"amount":{"type":"number"}}}}}},"example":{"booking":"66f1a2b3c4d5e6f708192a3b","reference":"STW-4821","venue":"Downtown Lockers","currency":"USD","total":45,"paid":20,"balance":25,"status":"partial","cancelled":false,"lines":[{"label":"Locker","amount":45}]}}}},"400":{"description":"ref is required — Empty reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"ref is required","path":"/client/stowbo/pay/{ref}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking <ref> not found — The reference matches no payment request, ledger row or booking.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <ref> not found","path":"/client/stowbo/pay/{ref}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"]},"post":{"operationId":"StowboClientController_payBooking","summary":"Pay what a payment reference owes","description":"Charges the **server-computed** balance for `ref` (a payment request pay token, a ledger row `sk`, or a booking id — see the GET). The client cannot name the amount. Pass `paymentMethodId` for a web card, or a confirmed `paymentIntentId` for native or wallet payments. A booking with nothing owing returns `alreadySettled: true` rather than an error.\n\nNo sign-in: a guest can settle from a payment link.\n\n#### Signature\n\n```http\nPOST /client/stowbo/pay/{ref} (ref: string, body) -> The payment summary after paying, with `paid` (amount charged) — or `alreadySettled: true`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated payment route; the amount is always computed on the server.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BOOKING_NOT_FOUND | Booking <ref> not found | The reference matches nothing. | List your bookings. |\n| `400` | REF_REQUIRED | ref is required | Empty reference. | — |\n| `402` | PAYMENT_REQUIRED | <currency> <balance> is due. | A balance is owed and neither `paymentMethodId` nor `paymentIntentId` was sent. The body carries `dueNow` and `currency`. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/pay/{ref}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","description":"Payment reference: a payment request pay token, a ledger row `sk`, or a booking id.","schema":{"type":"string"},"example":"stowbo-pr-7Kq2M9"}],"responses":{"201":{"description":"The payment summary after paying, with `paid` (amount charged) — or `alreadySettled: true`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"ref is required — Empty reference.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"ref is required","path":"/client/stowbo/pay/{ref}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"402":{"description":"<currency> <balance> is due. — A balance is owed and neither `paymentMethodId` nor `paymentIntentId` was sent. The body carries `dueNow` and `currency`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":402,"error":"<currency> <balance> is due.","path":"/client/stowbo/pay/{ref}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking <ref> not found — The reference matches nothing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking <ref> not found","path":"/client/stowbo/pay/{ref}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Browse"],"requestBody":{"description":"The payment.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentMethodId":{"type":"string","example":"pm_1Abc123"},"paymentIntentId":{"type":"string","example":"pi_3Abc123"}}},"example":{"paymentMethodId":"pm_1Abc123"}}}}}},"/client/stowbo/host/addons":{"get":{"operationId":"StowboClientController_myAddons","summary":"List my add-ons","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Add-ons","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/addons","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Every add-on the host owns.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/addons () -> Add-ons\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/addons`"},"post":{"operationId":"StowboClientController_createAddon","summary":"Create an add-on","description":"A host-owned extra — insurance, priority retrieval, a padlock.\n\n`price: 0` makes it a **free** add-on. `service: true` means the host physically does something, so buying it raises a fulfilment request rather than just adding a line.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/addons (body) -> The add-on\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `400` | NEGATIVE_PRICE | An add-on cannot cost less than nothing | `price` is negative. | Use `0` for free. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/host/listings/{listingId}/addons`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The add-on","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An add-on cannot cost less than nothing — `price` is negative.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An add-on cannot cost less than nothing","path":"/client/stowbo/host/addons","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/addons","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"The add-on.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"Priority retrieval"},"price":{"type":"number","description":"`0` is a free add-on.","example":500},"service":{"type":"boolean","description":"Raises a fulfilment request when bought.","example":true},"description":{"type":"string"}}},"example":{"name":"Priority retrieval","price":500,"service":true}}}}}},"/client/stowbo/host/addons/{addonId}":{"put":{"operationId":"StowboClientController_updateAddon","summary":"Update an add-on","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"addonId","required":true,"in":"path","schema":{"type":"string"},"description":"Add-on id.","example":"ADD-3"}],"responses":{"200":{"description":"The updated add-on","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/addons/{addonId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That add-on is not yours — It belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That add-on is not yours","path":"/client/stowbo/host/addons/{addonId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Add-on not found — No add-on has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Add-on not found","path":"/client/stowbo/host/addons/{addonId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Edits an add-on. Price changes apply to future purchases; add-ons already sold keep what they were sold at.\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/addons/{addonId} (addonId: string, body) -> The updated add-on\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ADDON_NOT_FOUND | Add-on not found | No add-on has that id. | List your add-ons. |\n| `403` | NOT_YOUR_ADDON | That add-on is not yours | It belongs to another host. | Check the id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /client/stowbo/host/addons/{addonId}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"price":600}}}}},"delete":{"operationId":"StowboClientController_deleteAddon","summary":"Retire an add-on","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"addonId","required":true,"in":"path","schema":{"type":"string"},"description":"Add-on id.","example":"ADD-3"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/addons/{addonId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Add-on not found — No add-on has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Add-on not found","path":"/client/stowbo/host/addons/{addonId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Soft-deletes an add-on: it stops being offered, but add-ons already sold keep working and the historical record stays intact.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/host/addons/{addonId} (addonId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Soft delete — existing purchases are unaffected.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ADDON_NOT_FOUND | Add-on not found | No add-on has that id. | Check the id. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/addons`"}},"/client/stowbo/host/listings/{listingId}/addons":{"put":{"operationId":"StowboClientController_setListingAddons","summary":"Set a listing's add-ons","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{listingId}/addons","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{listingId}/addons","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{listingId}/addons","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Sets which of the host's add-ons apply to one listing. The list **replaces** what was there — send the full set, not just the additions.\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/listings/{listingId}/addons (listingId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Replaces rather than appends.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/addons`","requestBody":{"description":"The add-ons that apply.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"addonIds":{"type":"array","items":{"type":"string"},"example":["ADD-3","ADD-7"]}}},"example":{"addonIds":["ADD-3","ADD-7"]}}}}}},"/client/stowbo/host/listings/{listingId}/fees":{"get":{"operationId":"StowboClientController_listingFees","summary":"List the fees on my listing","description":"The full `stowbo_fee` records attached to the listing, in the listing's order.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/listings/{listingId}/fees (listingId: string) -> Fee records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"Fee records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/listings/{listingId}/fees","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{listingId}/fees","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{listingId}/fees","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]},"post":{"operationId":"StowboClientController_createListingFee","summary":"Create a fee on my listing","description":"Saves the fee as entered (the `stowbo_fee` fields) as a host fee I own, and attaches it to the listing. The server sets only `type: host` and a unique `name` (from `name` when it is a lowercase slug, else from the title).\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/listings/{listingId}/fees (listingId: string, body) -> The fee record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `400` | NAME_REQUIRED | A name is required | No `title` or `label`. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"201":{"description":"The fee record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A name is required — No `title` or `label`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A name is required","path":"/client/stowbo/host/listings/{listingId}/fees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/listings/{listingId}/fees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/listings/{listingId}/fees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{listingId}/fees","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"`label` is accepted too."},"name":{"type":"string"},"amount":{"type":"number"},"basis":{"type":"string","enum":["fixed","percent"]},"appliesTo":{"type":"string","enum":["checkout","item"]},"paidBy":{"type":"string","enum":["guest","host"]},"status":{"type":"string","default":"active"}},"additionalProperties":true},"example":{"title":"Cleaning fee","amount":10,"basis":"fixed"}}}}},"put":{"operationId":"StowboClientController_setListingFees","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"{ listing, fees }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/listings/{listingId}/fees","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{listingId}/fees","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{listingId}/fees","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"Set the fees on my listing","description":"Replaces the listing's fee list with these fee **names** (attach and detach in one call).\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/listings/{listingId}/fees (listingId: string, body) -> { listing, fees }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The names are not checked against existing fees.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fees":{"type":"array","items":{"type":"string"}}}},"example":{"fees":["cleaning-fee"]}}}}}},"/client/stowbo/host/listings/{listingId}/fees/{feeId}":{"delete":{"operationId":"StowboClientController_removeListingFee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listingId","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"},{"name":"feeId","required":true,"in":"path","schema":{"type":"string"},"description":"`stowbo_fee` sk or name.","example":"cleaning-fee"}],"responses":{"200":{"description":"{ success, listing, fees }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/listings/{listingId}/fees/{feeId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{listingId}/fees/{feeId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{listingId}/fees/{feeId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"Detach a fee from my listing","description":"Removes the fee from the listing. Bookings that already carry it keep their line; the fee record itself stays.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/host/listings/{listingId}/fees/{feeId} (listingId: string, feeId: string) -> { success, listing, fees }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/host/fees/{feeId}":{"put":{"operationId":"StowboClientController_updateFee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"feeId","required":true,"in":"path","schema":{"type":"string"},"description":"`stowbo_fee` sk or name.","example":"cleaning-fee"}],"responses":{"200":{"description":"The fee record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/fees/{feeId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That fee is not yours — Another host owns the fee.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That fee is not yours","path":"/client/stowbo/host/fees/{feeId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Fee not found — No fee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Fee not found","path":"/client/stowbo/host/fees/{feeId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"Edit one of my fees","description":"Changes the fee's fields. `name` and `type` cannot be changed.\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/fees/{feeId} (feeId: string, body) -> The fee record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | FEE_NOT_FOUND | Fee not found | No fee has that id. | — |\n| `403` | NOT_YOUR_FEE | That fee is not yours | Another host owns the fee. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"amount":12}}}}}},"/client/stowbo/host/discounts":{"get":{"operationId":"StowboClientController_myDiscounts","summary":"List my coupons","description":"The `sf_discount` records this host owns.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/discounts () -> Coupons\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Coupons","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/discounts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]},"post":{"operationId":"StowboClientController_createHostDiscount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The coupon","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A code is required — No code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A code is required","path":"/client/stowbo/host/discounts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/discounts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"You need an approved 'stowbo-host' application before you can list space — The caller has no approved host application.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You need an approved 'stowbo-host' application before you can list space","path":"/client/stowbo/host/discounts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"Create a coupon","description":"Creates a coupon I own. The server scopes it to Stowbo and to my listings (`applyTo: stowbo`, `merchant` = me, `products` = the listings I pick, or all of mine now and later when empty) — a host coupon can never discount another host.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/discounts (body) -> The coupon\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `403` | NOT_APPROVED_HOST | You need an approved 'stowbo-host' application before you can list space | The caller has no approved host application. | Apply with `POST /client/stowbo/host/apply`. |\n| `400` | CODE_REQUIRED | A code is required | No code. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Upper-cased. Set on create only.","example":"FALL15"},"name":{"type":"string"},"type":{"type":"string","enum":["percent","fixed"]},"value":{"type":"number"},"maxDiscount":{"type":"number"},"minCartValue":{"type":"number"},"firstOrderOnly":{"type":"boolean"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number"},"usageLimitPerCustomer":{"type":"number"},"status":{"type":"string","default":"active"},"listings":{"type":"array","items":{"type":"string"},"description":"My listing names. Empty = all of mine."}}},"example":{"code":"FALL15","type":"percent","value":15,"listings":["downtown-lockers"]}}}}}},"/client/stowbo/host/discounts/{id}/stats":{"get":{"operationId":"StowboClientController_hostDiscountStats","summary":"Get a coupon's usage","description":"Every booking that used the code (read from the bills), what it gave away, revenue on the bookings still live, and use against its limits.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/discounts/{id}/stats (id: string) -> Usage and the bookings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | COUPON_NOT_FOUND | Coupon not found | No coupon has that id or code. | — |\n| `403` | NOT_YOUR_COUPON | That coupon is not yours | Another host owns the coupon. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Coupon (`sf_discount`) sk or code.","example":"FALL15"}],"responses":{"200":{"description":"Usage and the bookings","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"type":{"type":"string"},"value":{"type":"number"},"usageCount":{"type":"number"},"usageLimit":{"type":"number","nullable":true},"usageLimitPerCustomer":{"type":"number","nullable":true},"uses":{"type":"number"},"liveUses":{"type":"number"},"discounted":{"type":"number"},"revenue":{"type":"number"},"bookings":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/discounts/{id}/stats","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That coupon is not yours — Another host owns the coupon.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That coupon is not yours","path":"/client/stowbo/host/discounts/{id}/stats","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Coupon not found — No coupon has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Coupon not found","path":"/client/stowbo/host/discounts/{id}/stats","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/discounts/{id}":{"put":{"operationId":"StowboClientController_updateHostDiscount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Coupon (`sf_discount`) sk or code.","example":"FALL15"}],"responses":{"200":{"description":"The coupon","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/discounts/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That coupon is not yours — Another host owns the coupon.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That coupon is not yours","path":"/client/stowbo/host/discounts/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Coupon not found — No coupon has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Coupon not found","path":"/client/stowbo/host/discounts/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"Edit a coupon","description":"Changes a coupon I own. The code cannot be changed; `listings` rescopes it (my listings only).\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/discounts/{id} (id: string, body) -> The coupon\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | COUPON_NOT_FOUND | Coupon not found | No coupon has that id or code. | — |\n| `403` | NOT_YOUR_COUPON | That coupon is not yours | Another host owns the coupon. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Upper-cased. Set on create only.","example":"FALL15"},"name":{"type":"string"},"type":{"type":"string","enum":["percent","fixed"]},"value":{"type":"number"},"maxDiscount":{"type":"number"},"minCartValue":{"type":"number"},"firstOrderOnly":{"type":"boolean"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"usageLimit":{"type":"number"},"usageLimitPerCustomer":{"type":"number"},"status":{"type":"string","default":"active"},"listings":{"type":"array","items":{"type":"string"},"description":"My listing names. Empty = all of mine."}}},"example":{"value":20}}}}},"delete":{"operationId":"StowboClientController_deleteHostDiscount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Coupon (`sf_discount`) sk or code.","example":"FALL15"}],"responses":{"200":{"description":"{ success, id }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/discounts/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That coupon is not yours — Another host owns the coupon.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That coupon is not yours","path":"/client/stowbo/host/discounts/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Coupon not found — No coupon has that id or code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Coupon not found","path":"/client/stowbo/host/discounts/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"Retire a coupon","description":"Deactivates the coupon: the code stops working and bookings that used it keep their discount line.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/host/discounts/{id} (id: string) -> { success, id }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | COUPON_NOT_FOUND | Coupon not found | No coupon has that id or code. | — |\n| `403` | NOT_YOUR_COUPON | That coupon is not yours | Another host owns the coupon. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/stowbo/host/listings/{id}":{"get":{"operationId":"StowboClientController_myListing","summary":"Get one of my listings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"The listing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"A listing with its units and add-ons — **including drafts**, unlike the public view.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/listings/{id} (id: string) -> The listing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/host/listings/{id}`"},"put":{"operationId":"StowboClientController_updateListing","summary":"Edit a listing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"The updated listing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Nothing to update — The body has no changes.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Nothing to update","path":"/client/stowbo/host/listings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Changes a listing's details. Rate changes apply to future bookings; bookings already made keep the price they were sold at.\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/listings/{id} (id: string, body) -> The updated listing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n| `400` | NOTHING_TO_UPDATE | Nothing to update | The body has no changes. | Send at least one field. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/host/listings/{id}/status`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Mission garage bay (covered)"}}}}},"delete":{"operationId":"StowboClientController_deleteListing","summary":"Delete a listing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Removes a listing. Unlist it instead when there are bookings against it — deleting a listing people have booked leaves those bookings pointing at nothing.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/host/listings/{id} (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Prefer unlisting when bookings exist.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/host/listings/{id}/status`"}},"/client/stowbo/host/listings/{id}/bookings":{"get":{"operationId":"StowboClientController_listingBookings","summary":"Get a listing's bookings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"Bookings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}/bookings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}/bookings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}/bookings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Bookings that touch one of the host's listings — the listing dashboard.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/listings/{id}/bookings (id: string) -> Bookings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/today`"}},"/client/stowbo/host/bookings":{"get":{"operationId":"StowboClientController_hostAllBookings","summary":"List every booking at my places","description":"One list of every booking touching any of my listings, newest first: reference, customer, status, pay state, total, balance, discount, the first space and its window.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/bookings (status?: string) -> Booking rows\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"new | active | completed | cancelled | settled"}],"responses":{"200":{"description":"Booking rows","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/bookings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]},"post":{"operationId":"StowboClientController_hostBook","summary":"Create a booking for a customer","description":"Books on a customer's behalf. **Defaults to an OPEN stay**, since the end is often unknown when the booking is opened — pass `endDate` only when the window is genuinely known.\n\nA single detail is enough to identify the customer (a phone number or a plate) and no signup is required. Creates the order plus one booking item per unit. Set `movementIn` to record the first inbound movement in the same call.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings (body) -> The booking and its items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `409` | NO_CAPACITY | The space cannot take it right now | There is no capacity for the request. | Check `GET /client/stowbo/host/today`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/bookings/{bookingId}/items`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The booking and its items","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The space cannot take it right now — There is no capacity for the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"The space cannot take it right now","path":"/client/stowbo/host/bookings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"The booking.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"listing":{"type":"string","example":"LST-4821"},"units":{"type":"array","items":{"type":"string"},"example":["A1"]},"customer":{"type":"object","description":"A phone or plate is enough.","additionalProperties":true,"example":{"phone":"+15551234567"}},"startDate":{"type":"string","example":"2026-08-30"},"endDate":{"type":"string","description":"Omit for an open stay.","example":"2026-09-06"},"movementIn":{"type":"boolean","description":"Record the first inbound movement now.","example":true}}},"example":{"listing":"LST-4821","units":["A1"],"customer":{"phone":"+15551234567"},"movementIn":true}}}}}},"/client/stowbo/host/listings/{id}/calendar":{"get":{"operationId":"StowboClientController_hostCalendar","summary":"Get a listing's occupancy calendar","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-01"},{"name":"days","required":false,"in":"query","schema":{"type":"integer"},"example":30}],"responses":{"200":{"description":"The calendar","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}/calendar","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}/calendar","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}/calendar","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"A server-computed occupancy grid for one of the host's listings.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/listings/{id}/calendar (id: string, from?: string, days?: integer) -> The calendar\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/listings/{id}/occupancy-now`"}},"/client/stowbo/host/listings/{id}/blackouts":{"get":{"operationId":"StowboClientController_hostBlackouts","summary":"Get a listing's blackouts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"Blackouts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}/blackouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}/blackouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}/blackouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Days the host has closed on a listing.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/listings/{id}/blackouts (id: string) -> Blackouts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/listings/{id}/blackouts`"},"post":{"operationId":"StowboClientController_addBlackout","summary":"Close days on a listing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"201":{"description":"The blackout","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}/blackouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}/blackouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}/blackouts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Makes a stretch of days unavailable. Blackouts stop **new** bookings in that range; anything already booked into it stands, so check the calendar before closing days.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/listings/{id}/blackouts (id: string, body) -> The blackout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Does not cancel existing bookings in the range.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /client/stowbo/host/listings/{id}/blackouts/{blackoutId}`","requestBody":{"description":"The range to close.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","example":"2026-12-24"},"to":{"type":"string","example":"2026-12-26"},"reason":{"type":"string","example":"Closed for the holiday"}}},"example":{"from":"2026-12-24","to":"2026-12-26","reason":"Closed for the holiday"}}}}}},"/client/stowbo/host/listings/{id}/blackouts/{blackoutId}":{"delete":{"operationId":"StowboClientController_removeBlackout","summary":"Re-open blacked-out days","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"},{"name":"blackoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Blackout id.","example":"BLK-12"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}/blackouts/{blackoutId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}/blackouts/{blackoutId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}/blackouts/{blackoutId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Removes a blackout, putting those days back on the market.\n\n#### Signature\n\n```http\nDELETE /client/stowbo/host/listings/{id}/blackouts/{blackoutId} (id: string, blackoutId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/listings/{id}/blackouts`"}},"/client/stowbo/host/listings/{id}/status":{"put":{"operationId":"StowboClientController_setListingStatus","summary":"Publish, pause or unlist a listing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"The updated listing","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}/status","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}/status","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}/status","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Changes what a listing is doing on the market. Pausing stops new bookings; **bookings already taken are unaffected** and still have to be honoured.\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/listings/{id}/status (id: string, body) -> The updated listing\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing bookings still stand.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /client/stowbo/host/listings/{id}`","requestBody":{"description":"The status.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["published","paused","unlisted"],"example":"published"}}},"example":{"status":"published"}}}}}},"/client/stowbo/host/units/{id}":{"put":{"operationId":"StowboClientController_updateUnit","summary":"Edit a unit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Unit id.","example":"UNT-4821"}],"responses":{"200":{"description":"The updated unit","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/units/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your unit — The unit belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your unit","path":"/client/stowbo/host/units/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Changes a unit on one of the host's listings.\n\n#### Signature\n\n```http\nPUT /client/stowbo/host/units/{id} (id: string, body) -> The updated unit\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `403` | NOT_YOUR_UNIT | Not your unit | The unit belongs to another host. | Check the id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/units`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"A1 (corner)"}}}}}},"/client/stowbo/host/units":{"get":{"operationId":"StowboClientController_myUnits","summary":"List my units","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":false,"in":"query","schema":{"type":"string"},"example":"LST-4821"}],"responses":{"200":{"description":"Units","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/units","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"The host's physical units, optionally for one listing.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/units (listing?: string) -> Units\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/units`"},"post":{"operationId":"StowboClientController_createUnit","summary":"Add units to a listing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created units","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"Give either `names`, or `prefix` with `from` and `to` — The body mixes or omits the two forms.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give either `names`, or `prefix` with `from` and `to`","path":"/client/stowbo/host/units","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/units","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/units","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/units","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Adds physical units. Give either `names` (an explicit list) **or** `prefix` with `from` and `to` to generate a numbered range — not both.\n\nA range is capped at 500 units per call, which is what stops a typo in `to` creating tens of thousands of units.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/units (body) -> The created units\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n| `400` | UNIT_SPEC_INVALID | Give either `names`, or `prefix` with `from` and `to` | The body mixes or omits the two forms. | Pick one form. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /client/stowbo/host/units/{id}`","requestBody":{"description":"The units to create.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"listing":{"type":"string","example":"LST-4821"},"names":{"type":"array","items":{"type":"string"},"example":["A1","A2","A3"]},"prefix":{"type":"string","example":"A"},"from":{"type":"integer","example":1},"to":{"type":"integer","example":50}}},"examples":{"explicit":{"summary":"Named units","value":{"listing":"LST-4821","names":["A1","A2","A3"]}},"range":{"summary":"A generated range","value":{"listing":"LST-4821","prefix":"A","from":1,"to":50}}}}}}}},"/client/stowbo/host/resolve":{"get":{"operationId":"StowboClientController_resolve","summary":"Find a booking by any identifier","description":"One search across plate, phone, booking reference, unit, box number and tag. **The caller does not say which kind it is** — a scanner holds a barcode, a phone line holds whatever was read out.\n\nPartial matches are supported because identifiers arrive mistranscribed. Each result lists the transitions legal on it right now, so a caller need not re-derive the state machine to know what to offer.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/resolve (q?: string) -> Matches, each with its legal transitions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `400` | QUERY_TOO_SHORT | Search for at least 2 characters | `q` is shorter than 2 characters. | Type more. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/today`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"q","required":true,"in":"query","description":"At least 2 characters.","schema":{"type":"string"},"example":"7ABC"}],"responses":{"200":{"description":"Matches, each with its legal transitions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"Search for at least 2 characters — `q` is shorter than 2 characters.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Search for at least 2 characters","path":"/client/stowbo/host/resolve","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/resolve","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/today":{"get":{"operationId":"StowboClientController_today","summary":"Get today at my place","description":"Arrivals due, what is present, departures due, overdue, and capacity remaining **right now** — in one call rather than five.\n\nPer booking **item**, so twelve units can sit in twelve different lanes. Capacity is evaluated for this moment, because that is the question an unplanned arrival poses.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/today (listing?: string, date?: string) -> The day's lanes and current capacity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/resolve`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"listing","required":false,"in":"query","schema":{"type":"string"},"example":"LST-4821"},{"name":"date","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date. Defaults to today.","example":"2026-08-30"}],"responses":{"200":{"description":"The day's lanes and current capacity","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/today","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/bookings/{bookingId}/items":{"get":{"operationId":"StowboClientController_bookingItems","summary":"Get an order's items","description":"One record per thing booked, each with its own window, session, movements and status. Two can end on Tuesday, three on Friday, one can be disputed, and the rest run on — which is why the item, not the order, is the unit of work.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/bookings/{bookingId}/items (bookingId: string) -> Booking items\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/items/{itemId}/checkin`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"Booking items","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/items","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/items","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/items","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/items/{itemId}/checkin":{"post":{"operationId":"StowboClientController_checkIn","summary":"Check in an item","description":"Opens the session on **one booking item**. Distinct from creating the booking, which may have been weeks earlier, and from anything physically moving in — a session can be open with nothing in it yet.\n\nIdempotent: checking in twice is harmless.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/items/{itemId}/checkin (itemId: string, body) -> The item\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Idempotent.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `409` | SESSION_CLOSED | That session is closed | The item was already checked out. | A closed session cannot be reopened. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/items/{itemId}/movement`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The item","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/items/{itemId}/checkin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/host/items/{itemId}/checkin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"That session is closed — The item was already checked out.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"That session is closed","path":"/client/stowbo/host/items/{itemId}/checkin","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"Optional check-in detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"Guest arrived early"}}}}}},"/client/stowbo/host/items/{itemId}/movement":{"post":{"operationId":"StowboClientController_movement","summary":"Record a movement","description":"Records that something went in or out, inside one check-in. **Unlimited and append-only**: fourteen movements over a week is still one booking and one charge — movements are never billable and never touch capacity.\n\nEverything past `direction` is optional: photos, note, condition, signature, id, `declaredValue`, `releasedTo`, identifier, unit. The optional fields are what a custody dispute is settled on, so they are worth filling in at hand-over rather than reconstructing later.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/items/{itemId}/movement (itemId: string, body) -> The movement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Never billable; never releases the space.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `400` | DIRECTION_INVALID | direction must be 'in' or 'out' | `direction` is missing or invalid. | Send `in` or `out`. |\n| `409` | ALREADY_THERE | That is already here / That is already out | The movement repeats the last direction. | Check the current state. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/items/{itemId}/checkout`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The movement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"direction must be 'in' or 'out' — `direction` is missing or invalid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"direction must be 'in' or 'out'","path":"/client/stowbo/host/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/host/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"That is already here / That is already out — The movement repeats the last direction.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"That is already here / That is already out","path":"/client/stowbo/host/items/{itemId}/movement","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"The movement.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["direction"],"properties":{"direction":{"type":"string","enum":["in","out"],"example":"out"},"photos":{"type":"array","items":{"type":"object","additionalProperties":true}},"note":{"type":"string"},"condition":{"type":"string","example":"good"},"signature":{"type":"string"},"id":{"type":"string","description":"Identity document shown."},"declaredValue":{"type":"number","example":25000},"releasedTo":{"type":"string","description":"Who took it.","example":"Grace Hopper"},"identifier":{"type":"string","example":"7ABC123"},"unit":{"type":"string","example":"A1"}}},"example":{"direction":"out","releasedTo":"Grace Hopper","condition":"good","note":"Signed for at the desk"}}}}}},"/client/stowbo/host/items/{itemId}/assign":{"post":{"operationId":"StowboClientController_assign","summary":"Assign or move an item","description":"Puts a thing somewhere, or moves it. **Re-assignable on purpose**: where a thing sits can change any number of times during a session, and an assignment made at intake is not necessarily where it still is.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/items/{itemId}/assign (itemId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/listings/{id}/occupancy-now`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/items/{itemId}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/host/items/{itemId}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"Where it goes.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"unit":{"type":"string","example":"A1"},"note":{"type":"string"}}},"example":{"unit":"A1"}}}}}},"/client/stowbo/host/items/{itemId}/checkout":{"get":{"operationId":"StowboClientController_itemCheckoutStatement","summary":"Preview a check-out","description":"What closing this item will add to the bill. **The same computation check-out runs**, so the preview cannot disagree with what is charged. Writes nothing.\n\nAn open stay is priced start-to-exit; a fixed one accrues overstay past its window. `accruing` is true while a meter is still running, meaning the number will keep moving. `canCheckOut` is false while the thing is still present.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/items/{itemId}/checkout (itemId: string) -> The projected charge, with `accruing` and `canCheckOut`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/items/{itemId}/checkout`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"200":{"description":"The projected charge, with `accruing` and `canCheckOut`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"total":4200,"accruing":true,"canCheckOut":false}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/items/{itemId}/checkout","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/host/items/{itemId}/checkout","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]},"post":{"operationId":"StowboClientController_checkOut","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The closed item and what it cost","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/items/{itemId}/checkout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/host/items/{itemId}/checkout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Still present — move it out first — The last movement was inbound.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Still present — move it out first","path":"/client/stowbo/host/items/{itemId}/checkout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"Check out an item","description":"**A move-out is not a check-out.** One check-in holds any number of movements and the space stays held throughout; this is the only place a booking item stops occupying capacity.\n\nWhat it actually cost is added to the **order** as line items. Closing two of twelve leaves the other ten running, and the order completes only when every item has.\n\nRefused while the thing is still present — move it out first.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/items/{itemId}/checkout (itemId: string, body) -> The closed item and what it cost\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Releases capacity and bills the stay.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `409` | STILL_PRESENT | Still present — move it out first | The last movement was inbound. | Record an outbound movement. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/items/{itemId}/checkout`","requestBody":{"description":"Optional check-out detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"note":"All clear"}}}}}},"/client/stowbo/host/handover":{"get":{"operationId":"StowboClientController_hostHandover","summary":"Resolve a scanned pickup code","description":"Looks up a code scanned at the desk. A booking's own code resolves to the owner's items (minus any handed to someone else); a hand-off code resolves to its items and what must be checked (`verify[]`, `photoRequired`). `allowed` says whether anything can be handed over. Nothing is released here.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/handover (code?: string) -> { allowed, kind: owner\\|delegate, booking, delegation, collector, verify, verifyLabel, photoRequired, validUntil, note, items, … }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `400` | CODE_REQUIRED | code is required | No code. | — |\n| `404` | NO_PICKUP | No pickup matches that code | Unknown code. | — |\n| `403` | OTHER_HOST | That code belongs to another host | The items are at someone else's listing. | — |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/handover`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"},"example":"K7Q2M9"},{"name":"itemId","required":true,"in":"path","schema":{}}],"responses":{"200":{"description":"{ allowed, kind: owner|delegate, booking, delegation, collector, verify, verifyLabel, photoRequired, validUntil, note, items, … }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"code is required — No code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"code is required","path":"/client/stowbo/host/handover","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/handover","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That code belongs to another host — The items are at someone else's listing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That code belongs to another host","path":"/client/stowbo/host/handover","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No pickup matches that code — Unknown code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No pickup matches that code","path":"/client/stowbo/host/handover","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Still present — move it out first"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]},"post":{"operationId":"StowboClientController_hostCompleteHandover","summary":"Hand items over","description":"Completes a pickup: moves each item out and checks it out, stamping who collected it and what was verified. `verified` must cover what the pass demands; a hand-off also needs a photo.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/handover (body) -> { handedOver: [{ item, orderComplete }], collectedBy, verified, delegation }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `409` | NOT_ALLOWED | Nothing to hand over | The code resolves to nothing collectable (expired, revoked, already collected). The body `code` carries the reason. | — |\n| `400` | VERIFY_REQUIRED | Confirm <checks> before handing over | `verified` is missing a required check. The body carries `missing`. | — |\n\nPlus the standard platform errors: `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ handedOver: [{ item, orderComplete }], collectedBy, verified, delegation }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Confirm <checks> before handing over — `verified` is missing a required check. The body carries `missing`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Confirm <checks> before handing over","path":"/client/stowbo/host/handover","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/handover","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Nothing to hand over — The code resolves to nothing collectable (expired, revoked, already collected). The body `code` carries the reason.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Nothing to hand over","path":"/client/stowbo/host/handover","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"},"items":{"type":"array","items":{"type":"string"},"description":"Default: every item on the pass."},"verified":{"type":"array","items":{"type":"string","enum":["qr","name","id"]}},"photos":{"type":"array","items":{"type":"object","additionalProperties":true}},"note":{"type":"string"},"collectorName":{"type":"string"}}},"example":{"code":"K7Q2M9","verified":["qr","id"],"photos":[{"url":"https://files.example.com/handover.jpg"}]}}}}}},"/client/stowbo/host/items/{itemId}/refuse":{"post":{"operationId":"StowboClientController_hostRefusePickup","summary":"Refuse a pickup","description":"Records that a pickup was refused at the desk. Nothing moves; the owner is told.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/items/{itemId}/refuse (itemId: string, body) -> The refusal entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | ITEM_NOT_FOUND | Booking item not found | No booking item has that id. | List the order's items. |\n| `403` | NOT_YOUR_ITEM | Not your booking item | The item belongs to another customer. | Check the item id. |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking **item** id — one thing booked, not the whole order.","example":"ITM-4821"}],"responses":{"201":{"description":"The refusal entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/items/{itemId}/refuse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking item — The item belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking item","path":"/client/stowbo/host/items/{itemId}/refuse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking item not found — No booking item has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking item not found","path":"/client/stowbo/host/items/{itemId}/refuse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","enum":["wrong_person","expired","revoked","id_mismatch","other"],"description":"Anything else is recorded as `other`."},"who":{"type":"string"},"note":{"type":"string"},"delegation":{"type":"string"},"code":{"type":"string"}}},"example":{"reason":"id_mismatch","who":"Man claiming to be Sam"}}}}}},"/client/stowbo/host/bookings/{bookingId}/delegations":{"get":{"operationId":"StowboClientController_hostBookingDelegations","summary":"List the hand-offs on a booking I host","description":"Every hand-off on the booking, newest first.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/bookings/{bookingId}/delegations (bookingId: string) -> Hand-offs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"Hand-offs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"booking":{"type":"string"},"customer":{"type":"string"},"items":{"type":"array","items":{"type":"string"}},"itemDetails":{"type":"array","items":{"type":"object","additionalProperties":true}},"listing":{"type":"string"},"delegate":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"verify":{"type":"string"},"verifyLabel":{"type":"string"},"validUntil":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"code":{"type":"string","description":"The hand-off's own pickup code."},"status":{"type":"string","enum":["active","completed","revoked","expired"]},"sentAt":{"type":"string","nullable":true},"sentVia":{"type":"string","enum":["sms","email","both"]},"revokedAt":{"type":"string","nullable":true},"completedAt":{"type":"string","nullable":true},"collectedBy":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"passUrl":{"type":"string","description":"Returned on create and resend only."}}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/bookings/{bookingId}/delegations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/delegations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/delegations","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/handovers":{"get":{"operationId":"StowboClientController_hostHandovers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"listing","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"booking","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ handoffs, refusals, totals }","content":{"application/json":{"schema":{"type":"object","properties":{"handoffs":{"type":"array","items":{"type":"object","additionalProperties":true}},"refusals":{"type":"array","items":{"type":"object","additionalProperties":true}},"totals":{"type":"object","additionalProperties":true}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/handovers","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"summary":"List hand-offs at my places","description":"Hand-offs on my listings — who collected what, when, what was verified — plus refusals.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/handovers (from?: string, to?: string, listing?: string, status?: string, booking?: string, customer?: string) -> { handoffs, refusals, totals }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`."}},"/client/stowbo/host/listings/{id}/occupancy-now":{"get":{"operationId":"StowboClientController_occupancyNow","summary":"Get current occupancy","description":"What is in which unit right now. **Occupancy, not custody**: whether what is in a given unit is what is supposed to be there. Nothing here releases anything.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/listings/{id}/occupancy-now (id: string) -> Current occupancy by unit\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |\n| `403` | NOT_YOUR_LISTING | Not your listing | The listing belongs to another host. | Hosts can only act on their own listings. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/today`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Listing id.","example":"LST-4821"}],"responses":{"200":{"description":"Current occupancy by unit","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/listings/{id}/occupancy-now","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your listing — The listing belongs to another host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your listing","path":"/client/stowbo/host/listings/{id}/occupancy-now","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Listing not found — No listing has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Listing not found","path":"/client/stowbo/host/listings/{id}/occupancy-now","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/bookings/{bookingId}/charge":{"post":{"operationId":"StowboClientController_hostCharge","summary":"Add a charge to the bill","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The updated bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/charge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/charge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/charge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Adds a charge or credit line to an order — a damage fee, a late charge, a goodwill credit. It becomes part of what the guest owes.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/charge (bookingId: string, body) -> The updated bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Changes what the guest owes.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/bookings/{bookingId}/refund`","requestBody":{"description":"The charge.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","example":2500},"description":{"type":"string","example":"Damage to the door seal"}}},"example":{"amount":2500,"description":"Damage to the door seal"}}}}}},"/client/stowbo/host/bookings/{bookingId}/refund":{"post":{"operationId":"StowboClientController_hostRefund","summary":"Credit the bill","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The updated bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Adds a credit to an order, reducing what is owed or returning money already taken.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/refund (bookingId: string, body) -> The updated bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns money to the guest.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/bookings/{bookingId}/lines/{index}/reverse`","requestBody":{"description":"The credit.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","example":1000},"description":{"type":"string","example":"Goodwill — late retrieval"}}},"example":{"amount":1000,"description":"Goodwill — late retrieval"}}}}}},"/client/stowbo/host/bookings/{bookingId}/lines/{index}/reverse":{"post":{"operationId":"StowboClientController_hostReverse","summary":"Undo a bill line","description":"Reverses a line. **The original stays** and its opposite is added, so a disputed bill still explains itself rather than quietly no longer showing the mistake.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/lines/{index}/reverse (bookingId: string, index: string, body) -> The updated bill\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Adds an offsetting line; nothing is erased.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n| `409` | ALREADY_REVERSED | Already reversed | The line was already undone. | Nothing to do. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/bookings/{bookingId}/settle`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"},{"name":"index","required":true,"in":"path","schema":{"type":"string"},"description":"Line index on the order.","example":"2"}],"responses":{"201":{"description":"The updated bill","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/lines/{index}/reverse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/lines/{index}/reverse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/lines/{index}/reverse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Already reversed — The line was already undone.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already reversed","path":"/client/stowbo/host/bookings/{bookingId}/lines/{index}/reverse","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Charged in error"}}}}}},"/client/stowbo/host/bookings/{bookingId}/settle":{"post":{"operationId":"StowboClientController_settle","summary":"Settle an order and pay out","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The settlement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Closes the order financially and releases the host's payout. Do this once the bill is final — reversals after settlement are messier than getting the lines right first.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/settle (bookingId: string) -> The settlement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Releases money to the host.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/bookings/{bookingId}/hold`"}},"/client/stowbo/host/bookings/{bookingId}/hold":{"post":{"operationId":"StowboClientController_hostHold","summary":"Hold a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Holds the payout while something is unresolved — a damage claim, a dispute. The money stays put until released.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/hold (bookingId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/bookings/{bookingId}/release`","requestBody":{"description":"Why it is held.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Damage claim under review"}}}}}},"/client/stowbo/host/bookings/{bookingId}/release":{"post":{"operationId":"StowboClientController_hostRelease","summary":"Release a held payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/release","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/release","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/release","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Lifts a hold so the payout can proceed.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/release (bookingId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/bookings/{bookingId}/hold`"}},"/client/stowbo/host/bookings/{bookingId}/cancel":{"post":{"operationId":"StowboClientController_hostCancel","summary":"Cancel an order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"201":{"description":"The cancelled order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This is already checked in — check it out instead of cancelling — An item has been checked in.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This is already checked in — check it out instead of cancelling","path":"/client/stowbo/host/bookings/{bookingId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Cancels an order at the host's place. As with the guest-side cancel, this is for before anything has been handed over — once an item is checked in it must be checked out.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/cancel (bookingId: string, body) -> The cancelled order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n| `400` | ALREADY_CHECKED_IN | This is already checked in — check it out instead of cancelling | An item has been checked in. | Check it out. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/items/{itemId}/checkout`","requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Guest never arrived"}}}}}},"/client/stowbo/host/bookings/{bookingId}":{"get":{"operationId":"StowboClientController_hostBooking","summary":"Get an order at my place","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"One order at the host's listing.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/bookings/{bookingId} (bookingId: string) -> The order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/bookings/{bookingId}/items`"}},"/client/stowbo/host/bookings/{bookingId}/due":{"get":{"operationId":"StowboClientController_bookingDue","summary":"Get what a booking owes right now","description":"The billed balance plus any not-yet-billed meter on items still present — the number a \"Take payment · balance\" preset uses, so an open stay shows its real accrued amount. `action` asks the same question for `checkout`, `cancel` or `no_show`.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/bookings/{bookingId}/due (bookingId: string, action?: string) -> The payment summary plus the answer for that action\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"},{"name":"action","required":false,"in":"query","schema":{"type":"string","enum":["current","checkout","cancel","no_show"],"default":"current"}}],"responses":{"200":{"description":"The payment summary plus the answer for that action","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"action":{"type":"string"},"total":{"type":"number"},"paid":{"type":"number"},"balance":{"type":"number"},"currency":{"type":"string"},"accrued":{"type":"number","description":"Not-yet-billed meter on items still present."},"metered":{"type":"boolean"},"pastWindow":{"type":"boolean"},"lines":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Fee lines the action would add (cancellation / no-show fees)."},"feeTotal":{"type":"number"},"due":{"type":"number"},"refund":{"type":"number"},"payState":{"type":"string"},"message":{"type":"string"}}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/host/bookings/{bookingId}/due","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/due","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/due","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"]}},"/client/stowbo/host/bookings/{bookingId}/customer":{"get":{"operationId":"StowboClientController_bookingCustomer","summary":"Get an order's guest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"The guest contact card","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/customer","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/customer","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/customer","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"The guest's contact card — name, email, phone — for a booking at the host's own listing.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/bookings/{bookingId}/customer (bookingId: string) -> The guest contact card\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Personal contact details.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/bookings/{bookingId}/host`"}},"/client/stowbo/host/bookings/{bookingId}/requests/{requestId}":{"post":{"operationId":"StowboClientController_respondRequest","summary":"Respond to a guest request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"},{"name":"requestId","required":true,"in":"path","schema":{"type":"string"},"description":"Request id.","example":"REQ-12"}],"responses":{"201":{"description":"The request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/requests/{requestId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/requests/{requestId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/requests/{requestId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"Answers a guest's request — `acknowledged`, `ready`, `done` or `declined`. The guest is notified.\n\n#### Signature\n\n```http\nPOST /client/stowbo/host/bookings/{bookingId}/requests/{requestId} (bookingId: string, requestId: string, body) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Notifies the guest.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/stowbo/host/items/{itemId}/request`","requestBody":{"description":"The response.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["acknowledged","ready","done","declined"],"example":"ready"},"note":{"type":"string"}}},"example":{"status":"ready"}}}}}},"/client/stowbo/host/bookings/{bookingId}/timeline":{"get":{"operationId":"StowboClientController_hostTimeline","summary":"Get an order's timeline","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"The timeline","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/bookings/{bookingId}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"That booking is not for one of your listings — The booking is at another host's place.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That booking is not for one of your listings","path":"/client/stowbo/host/bookings/{bookingId}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/host/bookings/{bookingId}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"What happened to an order, in order — the record to read when a bill is questioned.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/bookings/{bookingId}/timeline (bookingId: string) -> The timeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_LISTINGS_BOOKING | That booking is not for one of your listings | The booking is at another host's place. | Check the booking id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/bookings/{bookingId}/timeline`"}},"/client/stowbo/host/earnings":{"get":{"operationId":"StowboClientController_earnings","summary":"Get my earnings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Earnings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Host authentication required — The caller is not signed in as a host.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Host authentication required","path":"/client/stowbo/host/earnings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Host"],"description":"What the host has earned — settled and pending.\n\n#### Signature\n\n```http\nGET /client/stowbo/host/earnings () -> Earnings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | HOST_AUTH_REQUIRED | Host authentication required | The caller is not signed in as a host. | Sign in with a host account. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/transactions`"}},"/client/stowbo/bookings/{bookingId}/timeline":{"get":{"operationId":"StowboClientController_guestTimeline","summary":"Get my booking's timeline","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking (order) id.","example":"BKG-4821"}],"responses":{"200":{"description":"The timeline","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Sign in required — No customer could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in required","path":"/client/stowbo/bookings/{bookingId}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your booking — The booking belongs to another customer.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your booking","path":"/client/stowbo/bookings/{bookingId}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/stowbo/bookings/{bookingId}/timeline","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Stowbo · Guest"],"description":"What happened to a booking, in order — check-ins, movements, charges, requests.\n\n#### Signature\n\n```http\nGET /client/stowbo/bookings/{bookingId}/timeline (bookingId: string) -> The timeline\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGN_IN_REQUIRED | Sign in required | No customer could be resolved from the token. | Sign in. |\n| `404` | BOOKING_NOT_FOUND | Booking not found | No booking has that id. | List your bookings. |\n| `403` | NOT_YOUR_BOOKING | Not your booking | The booking belongs to another customer. | Only the booking's customer can act on it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/stowbo/host/bookings/{bookingId}/timeline`"}},"/device-integrations/events":{"get":{"operationId":"DeviceIntegrationsController_events","summary":"Stream hub events","description":"A **Server-Sent Events** stream of events originating from the org's hubs. The response is an event stream, not JSON — read it incrementally.\n\nNote the org comes from the `orgid` **query parameter** here, since an `EventSource` cannot set headers. `kinds` filters which event types arrive.\n\n#### Signature\n\n```http\nGET /device-integrations/events (orgid?: string, hub?: string, kinds?: string) -> The event stream\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Tenant is taken from the query string.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /device-integrations/hubs/live-states`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"hub","required":false,"in":"query","schema":{"type":"string"},"example":"front-desk"},{"name":"kinds","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated event kinds.","example":"scan,print"},{"name":"orgid","in":"query","required":true,"description":"The org — passed as a query parameter because EventSource cannot send headers.","schema":{"type":"string"},"example":"org_4821"}],"responses":{"200":{"description":"The event stream","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/hubs/{sk}/api-key":{"post":{"operationId":"DeviceIntegrationsController_mintHubApiKey","summary":"Generate a hub API key","description":"Mints — or regenerates — the key a hub uses to connect. **Regenerating immediately invalidates the old key**, so the hub goes offline until it is reconfigured with the new one.\n\nThe key is returned once; it cannot be read back afterwards.\n\n#### Signature\n\n```http\nPOST /device-integrations/hubs/{sk}/api-key (sk: string) -> The new API key — shown once\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Disconnects the hub until it is reconfigured.\n- The key is not retrievable later.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /device-integrations/hubs/live-states`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sk","required":true,"in":"path","description":"Hub record key.","schema":{"type":"string"},"example":"HUB-4821"}],"responses":{"201":{"description":"The new API key — shown once","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/hubs/live-states":{"get":{"operationId":"DeviceIntegrationsController_hubLiveStates","summary":"Get live hub states","description":"Which hubs in the org are online right now — the map to check before dispatching to a device.\n\n#### Signature\n\n```http\nGET /device-integrations/hubs/live-states () -> Hub states\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /device-integrations/hubs/{hubName}/state`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Hub states","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/hubs/{hubName}/state":{"get":{"operationId":"DeviceIntegrationsController_hubState","summary":"Get a hub's diagnostic state","description":"Live diagnostics for one hub — connection, peripherals and recent activity.\n\n#### Signature\n\n```http\nGET /device-integrations/hubs/{hubName}/state (hubName: string) -> The hub state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /device-integrations/hubs/{hubName}/ping`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"hubName","required":true,"in":"path","description":"Hub name.","schema":{"type":"string"},"example":"front-desk"}],"responses":{"200":{"description":"The hub state","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/hubs/{hubName}/ping":{"post":{"operationId":"DeviceIntegrationsController_pingHub","summary":"Ping a hub","description":"Round-trips a message to a hub and reports the latency — proves the hub is genuinely reachable rather than merely last-seen recently.\n\n#### Signature\n\n```http\nPOST /device-integrations/hubs/{hubName}/ping (hubName: string) -> Latency and result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /device-integrations/hubs/{hubName}/state`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"hubName","required":true,"in":"path","schema":{"type":"string"},"description":"Hub name.","example":"front-desk"}],"responses":{"201":{"description":"Latency and result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/hubs/{hubName}/discover-paths":{"get":{"operationId":"DeviceIntegrationsController_discoverHubPaths","summary":"Discover device paths on a hub","description":"Lists the device files the hub can see — serial ports, USB devices. What to read when configuring a peripheral and you need its actual path.\n\n#### Signature\n\n```http\nGET /device-integrations/hubs/{hubName}/discover-paths (hubName: string) -> Device paths\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /device-integrations/hubs/{hubName}/endpoints`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"hubName","required":true,"in":"path","schema":{"type":"string"},"description":"Hub name.","example":"front-desk"}],"responses":{"200":{"description":"Device paths","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/hubs/{hubName}/endpoints":{"post":{"operationId":"DeviceIntegrationsController_setHubEndpoints","summary":"Configure a hub peripheral","description":"Adds or updates a peripheral on a hub remotely. The hub picks up the change without anyone visiting it — which also means a wrong device path silently breaks that peripheral until someone notices.\n\n#### Signature\n\n```http\nPOST /device-integrations/hubs/{hubName}/endpoints (hubName: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /device-integrations/hubs/{hubName}/discover-paths`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"hubName","required":true,"in":"path","schema":{"type":"string"},"description":"Hub name.","example":"front-desk"}],"requestBody":{"description":"The peripheral configuration.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"receipt-printer","kind":"escpos","path":"/dev/usb/lp0"}}}},"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/adapters":{"get":{"operationId":"DeviceIntegrationsController_list","summary":"List device adapters","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Adapters","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"description":"Every adapter the platform supports — what kinds of device can be driven at all.\n\n#### Signature\n\n```http\nGET /device-integrations/adapters () -> Adapters\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /device-integrations/adapters/configured`","tags":["Device integrations"]}},"/device-integrations/adapters/configured":{"get":{"operationId":"DeviceIntegrationsController_listConfigured","summary":"List configured adapters","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Configured adapters","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"description":"The adapters actually set up for this org — the list that says what can be called right now.\n\n#### Signature\n\n```http\nGET /device-integrations/adapters/configured () -> Configured adapters\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /device-integrations/call`","tags":["Device integrations"]}},"/device-integrations/call":{"post":{"operationId":"DeviceIntegrationsController_call","summary":"Dispatch a device call","description":"Sends a command to a physical device — page a guest, print a ticket, open a charger. The target is chosen by whichever of `adapter`, `device`, `servicePoint` or `location` is supplied, most specific first.\n\nThis makes hardware do something in the real world; there is no undo for a printed receipt or a triggered pager.\n\n#### Signature\n\n```http\nPOST /device-integrations/call (adapter?: string, device?: string, servicePoint?: string, location?: string, body) -> The device result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Drives physical hardware.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /device-integrations/page`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"adapter","required":false,"in":"query","schema":{"type":"string"},"example":"escpos"},{"name":"device","required":false,"in":"query","schema":{"type":"string"},"example":"receipt-printer"},{"name":"servicePoint","required":false,"in":"query","schema":{"type":"string"},"example":"SP-12"},{"name":"location","required":false,"in":"query","schema":{"type":"string"},"example":"LOC-3"}],"requestBody":{"description":"The command.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"action":"print","payload":{"lines":["Order A7K2M9QX4","Ready for collection"]}}}}},"responses":{"201":{"description":"The device result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Device integrations"]}},"/device-integrations/page":{"post":{"operationId":"DeviceIntegrationsController_page","summary":"Page a guest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"adapter","required":false,"in":"query","schema":{"type":"string"},"example":"sms"},{"name":"device","required":false,"in":"query","schema":{"type":"string"},"example":"pager-3"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"description":"Notifies a guest that they are being called — **SMS by default**, or pager hardware where it is configured. Outward-facing: it messages a real person.\n\n#### Signature\n\n```http\nPOST /device-integrations/page (adapter?: string, device?: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends a real message.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /checkin/{taskId}/notify`","tags":["Device integrations"],"requestBody":{"description":"Who to page and what to say.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"phone":"+15551234567","message":"Your table is ready."}}}}}},"/logistics/delivery/geocode":{"post":{"operationId":"DeliveryController_geocodeAddress","summary":"Geocode an address","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Coordinates, or `success: false` on failure","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"lat":{"type":"number"},"lng":{"type":"number"},"error":{"type":"string"}}},"examples":{"ok":{"summary":"Resolved","value":{"success":true,"lat":37.7936,"lng":-122.3958}},"failed":{"summary":"Not resolvable — still HTTP 200","value":{"success":false,"error":"Could not geocode address"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Turns a free-text address into coordinates.\n\n**Never returns an error status.** A failure comes back as HTTP 200 with `{ \"success\": false, \"error\": \"Could not geocode address\" }` — branch on `success`, not on the status code.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/geocode (body) -> Coordinates, or `success: false` on failure\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Failure is signalled in the body, not the status.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/validate-address`","requestBody":{"description":"The address.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string","example":"1 Market St, San Francisco, CA"}}},"example":{"address":"1 Market St, San Francisco, CA"}}}}}},"/logistics/delivery/validate-address":{"post":{"operationId":"DeliveryController_validateAddress","summary":"Validate an address","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The validation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Checks whether an address is deliverable, accepting either a string or a structured address. Worth running before creating a job — a job with an unresolvable dropoff wastes a driver's trip.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/validate-address (body) -> The validation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/quote`","requestBody":{"description":"The address.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"oneOf":[{"type":"string","example":"1 Market St, San Francisco, CA"},{"type":"object","properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zipCode":{"type":"string"},"country":{"type":"string"}}}]}}},"example":{"address":{"street":"1 Market St","city":"San Francisco","state":"CA","zipCode":"94105"}}}}}}},"/logistics/delivery/quote":{"post":{"operationId":"DeliveryController_getValidatedPriceQuote","summary":"Get a delivery price quote","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The quote","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No delivery config found — The org has no delivery configuration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No delivery config found","path":"/logistics/delivery/quote","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Prices a delivery from its stops, geocoding each one and applying the zone and distance rules. A quote, not a commitment — the price on a created job is recalculated from the job's own stops.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/quote (body) -> The quote\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NO_CONFIG | No delivery config found | The org has no delivery configuration. | Configure delivery first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs`","requestBody":{"description":"The stops to price.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["stops"],"properties":{"stops":{"type":"array","items":{"type":"object","required":["type","location"],"properties":{"type":{"type":"string","enum":["pickup","dropoff"],"example":"pickup"},"location":{"type":"object","properties":{"lat":{"type":"number","example":40.7128},"lng":{"type":"number","example":-74.006},"address":{"description":"A string, or a structured address.","oneOf":[{"type":"string","example":"1 Market St, San Francisco, CA"},{"type":"object","properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zipCode":{"type":"string"},"country":{"type":"string"}}}]}}}}}},"configName":{"type":"string","description":"Which delivery config to price against.","example":"default"}}},"example":{"stops":[{"type":"pickup","location":{"address":"1 Market St, San Francisco, CA"}},{"type":"dropoff","location":{"address":"500 Howard St, San Francisco, CA"}}]}}}}}},"/logistics/delivery/route-distance":{"post":{"operationId":"DeliveryController_getRouteDistance","summary":"Calculate route distance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The route distance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Road distance across a sequence of addresses, in order. Driving distance rather than straight-line — the figure that drives pricing.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/route-distance (body) -> The route distance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/quote`","requestBody":{"description":"The stops, in order.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["stops"],"properties":{"stops":{"type":"array","items":{"type":"object","required":["address"],"properties":{"address":{"type":"string","example":"1 Market St, San Francisco, CA"}}}}}},"example":{"stops":[{"address":"1 Market St, San Francisco, CA"},{"address":"500 Howard St, San Francisco, CA"}]}}}}}},"/logistics/delivery/config":{"get":{"operationId":"DeliveryController_getConfig","summary":"Get the delivery configuration","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":false,"in":"query","schema":{"type":"string"},"example":"default"}],"responses":{"200":{"description":"The configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No delivery config found — No configuration exists under that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No delivery config found","path":"/logistics/delivery/config","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"The org's delivery rules — pricing bands, distance rates, agent requirements. Pass `name` for a specific named config.\n\n#### Signature\n\n```http\nGET /logistics/delivery/config (name?: string) -> The configuration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NO_CONFIG | No delivery config found | No configuration exists under that name. | Check the name, or configure delivery. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/zones`"}},"/logistics/delivery/zones":{"get":{"operationId":"DeliveryController_listZones","summary":"List delivery zones","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Zones","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"The geographic zones deliveries are priced and dispatched within.\n\n#### Signature\n\n```http\nGET /logistics/delivery/zones (status?: string, page?: integer, pageSize?: integer) -> Zones\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/zones/lookup`"}},"/logistics/delivery/zones/lookup":{"get":{"operationId":"DeliveryController_findZoneForLocation","summary":"Find the zone for a location","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"lat","required":true,"in":"query","schema":{"type":"number"},"example":40.7128},{"name":"lng","required":true,"in":"query","schema":{"type":"number"},"example":-74.006}],"responses":{"200":{"description":"The containing zone, if any","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Resolves coordinates to the zone containing them — how a job gets its zone, and therefore its price band. A point outside every zone has no zone, which usually means the delivery cannot be served.\n\n#### Signature\n\n```http\nGET /logistics/delivery/zones/lookup (lat?: number, lng?: number) -> The containing zone, if any\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/zones/{zoneName}`"}},"/logistics/delivery/zones/{zoneName}":{"get":{"operationId":"DeliveryController_getZone","summary":"Get a delivery zone","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"zoneName","required":true,"in":"path","schema":{"type":"string"},"description":"Zone name.","example":"downtown"}],"responses":{"200":{"description":"The zone","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"One zone with its boundary and pricing.\n\n#### Signature\n\n```http\nGET /logistics/delivery/zones/{zoneName} (zoneName: string) -> The zone\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/zones`"}},"/logistics/delivery/agents":{"get":{"operationId":"DeliveryController_listAgents","summary":"List delivery agents","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Approval status.","example":"approved"},{"name":"availability","required":false,"in":"query","schema":{"type":"string"},"example":"online"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"courier"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Agents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Agents with their status, availability and type.\n\n#### Signature\n\n```http\nGET /logistics/delivery/agents (status?: string, availability?: string, type?: string, page?: integer, pageSize?: integer) -> Agents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/agents/online`"}},"/logistics/delivery/agents/online":{"get":{"operationId":"DeliveryController_getOnlineAgents","summary":"List online agents","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"lat","required":false,"in":"query","schema":{"type":"number"},"example":40.7128},{"name":"lng","required":false,"in":"query","schema":{"type":"number"},"example":-74.006},{"name":"radius","required":false,"in":"query","schema":{"type":"number"},"description":"Miles from the point.","example":5}],"responses":{"200":{"description":"Online agents","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Agents currently available, optionally near a point — the dispatcher's view of who can actually take a job right now.\n\n#### Signature\n\n```http\nGET /logistics/delivery/agents/online (lat?: number, lng?: number, radius?: number) -> Online agents\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/alert-agents`"}},"/logistics/delivery/agents/{agentId}":{"get":{"operationId":"DeliveryController_getAgent","summary":"Get a delivery agent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"The agent","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"One agent with their profile, status and performance.\n\n#### Signature\n\n```http\nGET /logistics/delivery/agents/{agentId} (agentId: string) -> The agent\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/agents/{agentId}/approve`"}},"/logistics/delivery/agents/{agentId}/location":{"put":{"operationId":"DeliveryController_updateAgentLocation","summary":"Update an agent's location","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}/location","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Records where an agent is. Called continuously by the driver app — this feeds proximity dispatch and live tracking, so a stale location means jobs are offered to the wrong drivers.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/agents/{agentId}/location (agentId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/agents/{agentId}/availability`","requestBody":{"description":"The location.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number","example":40.7128},"lng":{"type":"number","example":-74.006}}},"example":{"lat":40.7128,"lng":-74.006}}}}}},"/logistics/delivery/agents/{agentId}/availability":{"put":{"operationId":"DeliveryController_updateAgentAvailability","summary":"Update an agent's availability","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}/availability","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Sets an agent online or offline. Offline agents receive no job offers.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/agents/{agentId}/availability (agentId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/agents/online`","requestBody":{"description":"The availability.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"availability":{"type":"string","enum":["online","offline","busy"],"example":"online"}}},"example":{"availability":"online"}}}}}},"/logistics/delivery/agents/{agentId}/approve":{"put":{"operationId":"DeliveryController_approveAgent","summary":"Approve an agent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}/approve","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Approves an agent to take jobs. This is the gate between someone registering and them being able to collect a customer's goods — approve only after the background and document checks are done.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/agents/{agentId}/approve (agentId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Grants access to customer deliveries.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/agents/{agentId}/reject`","requestBody":{"description":"Optional approval notes.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"notes":"Licence and insurance verified"}}}}}},"/logistics/delivery/agents/{agentId}/suspend":{"put":{"operationId":"DeliveryController_suspendAgent","summary":"Suspend an agent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}/suspend","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Stops an approved agent taking new jobs. Jobs already in progress are not reassigned by this call — check for active jobs and reassign them, or the delivery stalls with a suspended driver holding the goods.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/agents/{agentId}/suspend (agentId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Does not reassign in-flight jobs.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/assign`","requestBody":{"description":"Why they are suspended.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Under investigation"}}}}}},"/logistics/delivery/agents/{agentId}/reject":{"put":{"operationId":"DeliveryController_rejectAgent","summary":"Reject an agent application","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}/reject","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Declines an agent's registration. A reason is worth giving — it is what the applicant sees.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/agents/{agentId}/reject (agentId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/agents/{agentId}/approve`","requestBody":{"description":"Why they were rejected.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Insurance documentation incomplete"}}}}}},"/logistics/delivery/agents/{agentId}/available-jobs":{"get":{"operationId":"DeliveryController_getAvailableJobsForAgent","summary":"List jobs available to an agent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"Available jobs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}/available-jobs","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Jobs this agent could take, filtered by their zone, vehicle type and requirements.\n\n#### Signature\n\n```http\nGET /logistics/delivery/agents/{agentId}/available-jobs (agentId: string) -> Available jobs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/offer`"}},"/logistics/delivery/agents/{agentId}/recalculate-performance":{"put":{"operationId":"DeliveryController_recalculateAgentPerformance","summary":"Recalculate an agent's performance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agentId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery agent id.","example":"AGT-4821"}],"responses":{"200":{"description":"The recalculated metrics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent not found — No agent has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent not found","path":"/logistics/delivery/agents/{agentId}/recalculate-performance","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Rebuilds an agent's performance metrics from their delivery history. Useful after correcting job data; the recalculated figures can change the agent's standing and job priority.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/agents/{agentId}/recalculate-performance (agentId: string) -> The recalculated metrics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AGENT_NOT_FOUND | Agent not found | No agent has that id. | List agents with `GET /logistics/delivery/agents`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/agents/{agentId}`"}},"/logistics/delivery/jobs":{"post":{"operationId":"DeliveryController_createJob","summary":"Create a delivery job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Job has no pickup location — The stops contain no pickup.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Job has no pickup location","path":"/logistics/delivery/jobs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Creates a job from its stops. A job is created **unassigned** — it still has to be broadcast, offered or assigned before a driver sees it.\n\n`pricing` and `driverPay` may be supplied to override the calculated figures; leave them out to let the zone and distance rules price the job.\n\nWith `requireSystemQuote: true` the stops are re-quoted on the server before the job is created — geocoded, checked for serviceability and routed — and the job is refused if any stop fails; the route distance and duration come from that quote, never from the client.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/jobs (body) -> The job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Created unassigned — dispatch it separately.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NO_PICKUP | Job has no pickup location | The stops contain no pickup. | Include a pickup stop. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/broadcast`","requestBody":{"description":"The job.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["stops"],"properties":{"customer":{"type":"object","properties":{"firstName":{"type":"string","example":"Ada"},"lastName":{"type":"string","example":"Lovelace"},"email":{"type":"string","example":"ada@example.com"},"phone":{"type":"string","example":"+15551234567"}}},"stops":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Pickup and dropoff stops, in order."},"requirements":{"type":"object","description":"Vehicle type, handling requirements.","additionalProperties":true},"scheduling":{"type":"object","description":"Windows and deadlines.","additionalProperties":true},"pricing":{"type":"object","description":"Overrides the calculated price.","additionalProperties":true},"driverPay":{"type":"object","description":"Overrides the calculated driver pay.","additionalProperties":true},"notes":{"type":"string","description":"Internal."},"customerNotes":{"type":"string","description":"Visible to the customer."},"payment":{"type":"object","additionalProperties":true},"config":{"type":"string","description":"Delivery config to price against. Defaults to the default config.","example":"default"},"requireSystemQuote":{"type":"boolean","description":"Re-quote and validate the stops on the server; refuse the job if they fail."}}},"example":{"customer":{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com","phone":"+15551234567"},"stops":[{"type":"pickup","location":{"address":"1 Market St, San Francisco, CA"}},{"type":"dropoff","location":{"address":"500 Howard St, San Francisco, CA"}}],"customerNotes":"Ring the bell twice"}}}}},"get":{"operationId":"DeliveryController_listJobs","summary":"List delivery jobs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"assigned"},{"name":"agentId","required":false,"in":"query","schema":{"type":"string"},"example":"AGT-4821"},{"name":"customerEmail","required":false,"in":"query","schema":{"type":"string"},"example":"ada@example.com"},{"name":"zone","required":false,"in":"query","schema":{"type":"string"},"example":"downtown"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Jobs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Jobs with their status, filterable by agent, customer and zone — the dispatch board.\n\n#### Signature\n\n```http\nGET /logistics/delivery/jobs (status?: string, agentId?: string, customerEmail?: string, zone?: string, page?: integer, pageSize?: integer) -> Jobs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/jobs/{jobId}`"}},"/logistics/delivery/jobs/{jobId}":{"get":{"operationId":"DeliveryController_getJob","summary":"Get a delivery job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"One job with its stops, agent, status and pricing.\n\n#### Signature\n\n```http\nGET /logistics/delivery/jobs/{jobId} (jobId: string) -> The job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/jobs/{jobId}/tracking`"}},"/logistics/delivery/jobs/{jobId}/broadcast":{"put":{"operationId":"DeliveryController_broadcastJob","summary":"Broadcast a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/broadcast","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Opens a job to every eligible agent — first to accept takes it. Use this when speed matters more than choosing the driver; `offer` targets one agent instead.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/broadcast (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/logistics/delivery/jobs/{jobId}/offer":{"put":{"operationId":"DeliveryController_offerJob","summary":"Offer a job to an agent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/offer","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Offers a job to a specific agent, who can accept or reject it. Unlike a broadcast, the job is held for them rather than raced for.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/offer (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/broadcast`","requestBody":{"description":"Which agent.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["agentId"],"properties":{"agentId":{"type":"string","example":"AGT-4821"}}},"example":{"agentId":"AGT-4821"}}}}}},"/logistics/delivery/jobs/{jobId}/assign":{"put":{"operationId":"DeliveryController_assignJob","summary":"Assign a job to an agent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/assign","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Assigns a job directly, with no offer or acceptance step — the dispatcher's override. The agent gets the job whether or not they would have accepted it.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/assign (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Bypasses agent acceptance.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/offer`","requestBody":{"description":"Which agent.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["agentId"],"properties":{"agentId":{"type":"string","example":"AGT-4821"}}},"example":{"agentId":"AGT-4821"}}}}}},"/logistics/delivery/jobs/{jobId}/alert-agents":{"post":{"operationId":"DeliveryController_sendJobAlertToAgents","summary":"Alert agents about a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/alert-agents","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Pushes a notification about a job to a chosen set of agents — by explicit ids, by zone, by online status, or by radius from a point. **Sends real notifications to drivers**, so a broad filter reaches a lot of people at once.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/jobs/{jobId}/alert-agents (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends push notifications — check the filter width first.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/agents/online`","requestBody":{"description":"Who to alert. Combine the filters as needed.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agentIds":{"type":"array","items":{"type":"string"},"example":["AGT-4821"]},"zone":{"type":"string","example":"downtown"},"online":{"type":"boolean","example":true},"radius":{"type":"object","properties":{"lat":{"type":"number","example":40.7128},"lng":{"type":"number","example":-74.006},"miles":{"type":"number","example":5}}}}},"examples":{"specific":{"summary":"Specific agents","value":{"agentIds":["AGT-4821","AGT-4822"]}},"nearby":{"summary":"Online agents within 5 miles","value":{"online":true,"radius":{"lat":40.7128,"lng":-74.006,"miles":5}}}}}}}}},"/logistics/delivery/jobs/{jobId}/accept":{"put":{"operationId":"DeliveryController_acceptJob","summary":"Accept a job on an agent's behalf","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/accept","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Job is no longer available — Another agent already took it.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Job is no longer available","path":"/logistics/delivery/jobs/{jobId}/accept","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Records that an agent took the job. Racy by nature — a broadcast job already taken returns 409.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/accept (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n| `409` | JOB_UNAVAILABLE | Job is no longer available | Another agent already took it. | Offer the agent a different job. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/reject`","requestBody":{"description":"Which agent.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["agentId"],"properties":{"agentId":{"type":"string","example":"AGT-4821"}}},"example":{"agentId":"AGT-4821"}}}}}},"/logistics/delivery/jobs/{jobId}/reject":{"put":{"operationId":"DeliveryController_rejectJob","summary":"Reject a job on an agent's behalf","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/reject","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Records that an agent declined an offered job, returning it to the pool.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/reject (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/broadcast`","requestBody":{"description":"Which agent, and why.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"agentId":"AGT-4821","reason":"Too far"}}}}}},"/logistics/delivery/jobs/{jobId}/start-pickup":{"put":{"operationId":"DeliveryController_startPickup","summary":"Start the pickup leg","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/start-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Marks the driver as en route to the pickup. Starts the customer-visible tracking for that leg.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/start-pickup (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/logistics/delivery/jobs/{jobId}/arrive-pickup":{"put":{"operationId":"DeliveryController_arrivePickup","summary":"Arrive at pickup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/arrive-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Marks the driver as arrived at the pickup, stamping the arrival time — which is what any waiting-time charge is calculated from.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/arrive-pickup (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/adjustments`","requestBody":{"description":"Optional arrival detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"lat":40.7128,"lng":-74.006}}}}}},"/logistics/delivery/jobs/{jobId}/complete-pickup":{"put":{"operationId":"DeliveryController_completePickup","summary":"Complete the pickup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/complete-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Records that the goods were collected. Attach pickup photos through the images endpoint — this is the point at which custody passes to the driver.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/complete-pickup (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/images`","requestBody":{"description":"Optional pickup detail — signature, notes.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"notes":"Two sealed boxes"}}}}}},"/logistics/delivery/jobs/{jobId}/start-dropoff":{"put":{"operationId":"DeliveryController_startDropoff","summary":"Start the dropoff leg","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/start-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Marks the driver as en route to the dropoff.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/start-dropoff (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/logistics/delivery/jobs/{jobId}/arrive-dropoff":{"put":{"operationId":"DeliveryController_arriveDropoff","summary":"Arrive at dropoff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/arrive-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Marks the driver as arrived at the dropoff.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/arrive-dropoff (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"Optional arrival detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"lat":37.7879,"lng":-122.3972}}}}}},"/logistics/delivery/jobs/{jobId}/complete-dropoff":{"put":{"operationId":"DeliveryController_completeDropoff","summary":"Complete the dropoff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/complete-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Records the handover — recipient name, signature, notes. Proof-of-delivery photos go through the images endpoint with category `proof`; without them a disputed delivery has nothing to stand on.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/complete-dropoff (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/images`","requestBody":{"description":"Handover detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"receivedBy":"Ada Lovelace","notes":"Left with reception"}}}}}},"/logistics/delivery/jobs/{jobId}/complete":{"put":{"operationId":"DeliveryController_completeJob","summary":"Complete a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/complete","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Closes the job as delivered. Payment is a separate step — completing does not charge the customer or release driver pay.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/complete (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/process-payment`"}},"/logistics/delivery/jobs/{jobId}/cancel":{"put":{"operationId":"DeliveryController_cancelJob","summary":"Cancel a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The cancelled job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Job has no completed payment to refund — `refund` was requested but nothing was ever charged.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Job has no completed payment to refund","path":"/logistics/delivery/jobs/{jobId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Not authorized to cancel this job — The caller may not cancel this job.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to cancel this job","path":"/logistics/delivery/jobs/{jobId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Cancels a delivery. `cancelledBy` records who called it off, which drives who bears any cancellation fee.\n\n**Refunds happen here.** `refund: true` with either `refundAmount` or `refundPercent` returns money to the customer; omit them and nothing is refunded even though the job is cancelled. A job with no completed payment cannot be refunded.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/cancel (jobId: string, body) -> The cancelled job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Refunds money when asked to — and only when asked to.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n| `403` | NOT_AUTHORIZED_CANCEL | Not authorized to cancel this job | The caller may not cancel this job. | Cancel as the customer, assigned agent or an admin. |\n| `400` | NO_PAYMENT_TO_REFUND | Job has no completed payment to refund | `refund` was requested but nothing was ever charged. | Cancel without a refund. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/fail`","requestBody":{"description":"The cancellation.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["cancelledBy","reason"],"properties":{"cancelledBy":{"type":"string","enum":["customer","agent","admin","system"],"example":"customer"},"reason":{"type":"string","example":"No longer needed"},"refund":{"type":"boolean","example":true},"refundAmount":{"type":"number","description":"Absolute refund.","example":1200},"refundPercent":{"type":"number","description":"Percentage refund.","example":100}}},"examples":{"noRefund":{"summary":"Cancel with no refund","value":{"cancelledBy":"admin","reason":"Duplicate booking"}},"fullRefund":{"summary":"Cancel and refund in full","value":{"cancelledBy":"customer","reason":"No longer needed","refund":true,"refundPercent":100}}}}}}}},"/logistics/delivery/jobs/{jobId}/fail":{"put":{"operationId":"DeliveryController_failJob","summary":"Mark a job failed","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/fail","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Records a delivery that could not be completed — nobody home, refused, inaccessible. Distinct from a cancellation, which is a decision not to deliver; a failure means the attempt was made, and the two are settled differently.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/fail (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/cancel`","requestBody":{"description":"Why it failed.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Recipient not available after three attempts"}}}}}},"/logistics/delivery/jobs/{jobId}/pricing":{"put":{"operationId":"DeliveryController_updateJobPricing","summary":"Update a job's pricing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/pricing","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Overrides the calculated price for a job. Changes what the customer is charged and, depending on the split, what the driver is paid — prefer an adjustment when the change is an addition rather than a correction, because adjustments are itemised and this is not.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/pricing (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Replaces the price without an itemised trail.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/adjustments`","requestBody":{"description":"The new pricing.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"total":3200,"base":2500,"distance":700}}}}}},"/logistics/delivery/jobs/{jobId}/images":{"get":{"operationId":"DeliveryController_getJobImages","summary":"Get job images","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"},{"name":"category","required":false,"in":"query","schema":{"type":"string"},"description":"`proof`, `pickup`, `dropoff`, `issue`, `damage`, `other`.","example":"proof"}],"responses":{"200":{"description":"Images","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/images","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Photos attached to a job, optionally by category — the proof-of-delivery record.\n\n#### Signature\n\n```http\nGET /logistics/delivery/jobs/{jobId}/images (jobId: string, category?: string) -> Images\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/images`"},"post":{"operationId":"DeliveryController_addJobImages","summary":"Add job images","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/images","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Attaches already-uploaded images to a job under a category. `proof` is the category that backs a delivery against a dispute; `damage` and `issue` support a claim. `stopIndex` ties an image to a specific stop on a multi-stop job.\n\nUpload the file first — this endpoint takes paths and URLs, not file data.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/jobs/{jobId}/images (jobId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/complete-dropoff`","requestBody":{"description":"The images.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["images","category"],"properties":{"images":{"type":"array","items":{"type":"object","required":["path","url"],"properties":{"path":{"type":"string","example":"jobs/4821/proof-1.jpg"},"url":{"type":"string","example":"https://cdn.example.com/jobs/4821/proof-1.jpg"},"contentType":{"type":"string","example":"image/jpeg"}}}},"category":{"type":"string","enum":["proof","pickup","dropoff","issue","damage","other"],"example":"proof"},"stopIndex":{"type":"integer","description":"Which stop the images belong to.","example":1}}},"example":{"images":[{"path":"jobs/4821/proof-1.jpg","url":"https://cdn.example.com/jobs/4821/proof-1.jpg"}],"category":"proof","stopIndex":1}}}}}},"/logistics/delivery/jobs/{jobId}/adjustments":{"get":{"operationId":"DeliveryController_getJobAdjustments","summary":"Get job adjustments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"Adjustments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/adjustments","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"The itemised charges and credits on a job — waiting fees, tolls, damage charges, bonuses.\n\n#### Signature\n\n```http\nGET /logistics/delivery/jobs/{jobId}/adjustments (jobId: string) -> Adjustments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/adjustments`"},"post":{"operationId":"DeliveryController_addJobAdjustment","summary":"Add a job adjustment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The adjustment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid amount — The amount is missing or not positive.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid amount","path":"/logistics/delivery/jobs/{jobId}/adjustments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/adjustments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Adds an itemised charge or credit. `type` says which direction it goes — `debit` charges, `credit` refunds or rewards.\n\n`driverPortion` decides how much of the adjustment reaches the driver. A damage charge with no `driverPortion` is borne entirely by the platform; a bonus with the full amount goes wholly to the driver. Getting this wrong misallocates money between the platform and the courier.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/jobs/{jobId}/adjustments (jobId: string, body) -> The adjustment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `driverPortion` allocates the money — set it deliberately.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n| `400` | INVALID_AMOUNT | Invalid amount | The amount is missing or not positive. | Use `type` for direction; keep the amount positive. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/adjustments/{adjustmentId}/remove`","requestBody":{"description":"The adjustment.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["category","type","amount"],"properties":{"category":{"type":"string","enum":["cleaning_fee","toll_fee","parking_fee","waiting_fee","damage_charge","cancellation_fee","bonus","rebate","discount","refund","penalty","other"],"example":"waiting_fee"},"type":{"type":"string","enum":["debit","credit"],"example":"debit"},"amount":{"type":"number","example":500},"driverPortion":{"type":"number","description":"How much of the amount goes to the driver.","example":500},"description":{"type":"string","example":"20 minutes waiting at pickup"},"notes":{"type":"string"}}},"example":{"category":"waiting_fee","type":"debit","amount":500,"driverPortion":500,"description":"20 minutes waiting at pickup"}}}}}},"/logistics/delivery/jobs/{jobId}/adjustments/{adjustmentId}/remove":{"put":{"operationId":"DeliveryController_removeJobAdjustment","summary":"Remove a job adjustment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"},{"name":"adjustmentId","required":true,"in":"path","schema":{"type":"string"},"description":"Adjustment id.","example":"ADJ-12"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/adjustments/{adjustmentId}/remove","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Removes an adjustment from a job. If payment has already been processed, removing it changes the job total without reversing what was charged — reconcile separately.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/adjustments/{adjustmentId}/remove (jobId: string, adjustmentId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Does not reverse an already-processed charge.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/jobs/{jobId}/payment-status`"}},"/logistics/delivery/jobs/{jobId}/payment-status":{"get":{"operationId":"DeliveryController_getJobPaymentStatus","summary":"Get a job's payment status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The payment status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/payment-status","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Whether the job has been charged, and for how much. Read this before processing payment to avoid double-charging.\n\n#### Signature\n\n```http\nGET /logistics/delivery/jobs/{jobId}/payment-status (jobId: string) -> The payment status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/process-payment`"}},"/logistics/delivery/jobs/{jobId}/process-payment":{"post":{"operationId":"DeliveryController_processJobPayment","summary":"Process a job payment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The payment result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Job must be completed before processing payment — The job has not been completed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Job must be completed before processing payment","path":"/logistics/delivery/jobs/{jobId}/process-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Not authorized to complete this payment — The caller may not process this job's payment.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to complete this payment","path":"/logistics/delivery/jobs/{jobId}/process-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/process-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Charges the customer for a completed job, including its adjustments. The job must be completed first.\n\n`forceZeroPayment` settles a job at zero — for a fully comped delivery. It bypasses the amount check, so use it only when the job genuinely should cost nothing.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/jobs/{jobId}/process-payment (jobId: string, body) -> The payment result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Charges the customer.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n| `400` | JOB_NOT_COMPLETED | Job must be completed before processing payment | The job has not been completed. | Complete the delivery first. |\n| `403` | NOT_AUTHORIZED_PAYMENT | Not authorized to complete this payment | The caller may not process this job's payment. | An admin or the merchant must process it. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/jobs/{jobId}/payment-status`","requestBody":{"description":"Options.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"forceZeroPayment":{"type":"boolean","description":"Settle at zero.","example":false}}},"example":{}}}}}},"/logistics/delivery/jobs/{jobId}/tracking":{"put":{"operationId":"DeliveryController_updateJobTracking","summary":"Update job live tracking","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/tracking","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Pushes the driver's current position onto the job, which is what a customer's tracking map shows. Called frequently while a job is in progress; the position is visible to the customer.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/tracking (jobId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Customer-visible.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/jobs/{jobId}`","requestBody":{"description":"The position.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lat":{"type":"number","example":40.7128},"lng":{"type":"number","example":-74.006},"heading":{"type":"number","example":92},"eta":{"type":"string","format":"date-time"}}},"example":{"lat":40.7128,"lng":-74.006,"heading":92}}}}}},"/logistics/delivery/jobs/{jobId}/issues":{"post":{"operationId":"DeliveryController_reportIssue","summary":"Report a job issue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The issue","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/issues","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Raises a problem on a job — damage, access, a wrong address. `reportedBy` records the perspective, which matters when the driver and the customer describe the same event differently. Photos attached here support any later claim.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/jobs/{jobId}/issues (jobId: string, body) -> The issue\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/issues/{issueId}/resolve`","requestBody":{"description":"The issue.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reportedBy","type"],"properties":{"reportedBy":{"type":"string","enum":["driver","customer","recipient","admin"],"example":"driver"},"type":{"type":"string","example":"access_denied"},"description":{"type":"string","example":"Building requires a code nobody provided"},"photos":{"type":"array","items":{"type":"object","additionalProperties":true}},"location":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}}}}},"example":{"reportedBy":"driver","type":"access_denied","description":"Building requires a code nobody provided"}}}}},"get":{"operationId":"DeliveryController_getJobIssues","summary":"Get job issues","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"Issues","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/issues","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Problems raised on a job and their state.\n\n#### Signature\n\n```http\nGET /logistics/delivery/jobs/{jobId}/issues (jobId: string) -> Issues\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/issues`"}},"/logistics/delivery/jobs/{jobId}/issues/{issueId}/resolve":{"put":{"operationId":"DeliveryController_resolveIssue","summary":"Resolve a job issue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"},{"name":"issueId","required":true,"in":"path","schema":{"type":"string"},"description":"Issue id.","example":"ISS-12"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/issues/{issueId}/resolve","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Closes an issue with a resolution. Record what was actually done — the resolution is the record if the delivery is disputed later.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/issues/{issueId}/resolve (jobId: string, issueId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/issues/{issueId}/escalate`","requestBody":{"description":"The resolution.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"resolution":"Customer supplied the door code; delivery completed"}}}}}},"/logistics/delivery/jobs/{jobId}/issues/{issueId}/escalate":{"put":{"operationId":"DeliveryController_escalateIssue","summary":"Escalate a job issue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"},{"name":"issueId","required":true,"in":"path","schema":{"type":"string"},"description":"Issue id.","example":"ISS-12"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/issues/{issueId}/escalate","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Raises an issue to support. Use it when the resolution needs someone with authority to compensate or reassign, rather than the driver on the ground.\n\n#### Signature\n\n```http\nPUT /logistics/delivery/jobs/{jobId}/issues/{issueId}/escalate (jobId: string, issueId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /logistics/delivery/jobs/{jobId}/issues/{issueId}/resolve`"}},"/logistics/delivery/jobs/{jobId}/messages":{"post":{"operationId":"DeliveryController_sendMessage","summary":"Send a job message","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Posts a message on a job's thread — the channel between driver, customer, recipient and support. `sender` records the role, and the message reaches the other parties, so it is outward-facing.\n\n#### Signature\n\n```http\nPOST /logistics/delivery/jobs/{jobId}/messages (jobId: string, body) -> The message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Reaches the customer and driver.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/jobs/{jobId}/messages`","requestBody":{"description":"The message.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["sender","content"],"properties":{"sender":{"type":"string","enum":["driver","customer","recipient","support","system"],"example":"support"},"senderName":{"type":"string","example":"Support"},"content":{"type":"string","example":"The driver is 5 minutes away."},"type":{"type":"string","enum":["text","image","location","eta_update","status_update"],"example":"text"},"attachment":{"type":"object","additionalProperties":true}}},"example":{"sender":"support","content":"The driver is 5 minutes away.","type":"text"}}}}},"get":{"operationId":"DeliveryController_getJobMessages","summary":"Get job messages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/logistics/delivery/jobs/{jobId}/messages","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"The message thread for a job.\n\n#### Signature\n\n```http\nGET /logistics/delivery/jobs/{jobId}/messages (jobId: string) -> Messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id. | List jobs with `GET /logistics/delivery/jobs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /logistics/delivery/jobs/{jobId}/messages`"}},"/logistics/delivery/stats":{"get":{"operationId":"DeliveryController_getStats","summary":"Get delivery statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics"],"description":"Delivery volume, completion rate, average times and agent utilisation.\n\n#### Signature\n\n```http\nGET /logistics/delivery/stats () -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /logistics/delivery/jobs`"}},"/client/logistics/init":{"get":{"operationId":"DeliveryClientController_getInitData","summary":"Get client init data","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Init payload","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/init","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Everything a logistics client needs on start-up in one call — the caller's agent profile if they have one, delivery configuration and current state. Saves a cold client several round trips.\n\n#### Signature\n\n```http\nGET /client/logistics/init () -> Init payload\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/me`"}},"/client/logistics/register":{"post":{"operationId":"DeliveryClientController_registerAsAgent","summary":"Register as a delivery driver","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The agent profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Registers the calling customer as a delivery agent with their vehicle details. Registration does **not** grant the ability to take jobs — an operator still has to approve the agent, so expect a pending state after this succeeds.\n\n#### Signature\n\n```http\nPOST /client/logistics/register (body) -> The agent profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Approval is a separate operator action.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/me`","requestBody":{"description":"Vehicle details.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicleType":{"type":"string","example":"car"},"vehicleMake":{"type":"string","example":"Toyota"},"vehicleModel":{"type":"string","example":"Corolla"},"vehicleYear":{"type":"integer","example":2021},"vehicleColor":{"type":"string","example":"blue"},"licensePlate":{"type":"string","example":"7ABC123"},"licensePlateState":{"type":"string","example":"CA"}}},"example":{"vehicleType":"car","vehicleMake":"Toyota","vehicleModel":"Corolla","vehicleYear":2021,"licensePlate":"7ABC123","licensePlateState":"CA"}}}}}},"/client/logistics/me":{"get":{"operationId":"DeliveryClientController_getMyProfile","summary":"Get my agent profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The agent profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/me","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/me","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"The caller's own driver profile — approval status, vehicle, performance and earnings.\n\n#### Signature\n\n```http\nGET /client/logistics/me () -> The agent profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/logistics/availability`"}},"/client/logistics/availability":{"put":{"operationId":"DeliveryClientController_updateAvailability","summary":"Set my availability","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/availability","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/availability","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Goes online or offline. Offline drivers receive no job offers — this is the switch a driver flips at the start and end of a shift.\n\n#### Signature\n\n```http\nPUT /client/logistics/availability (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/jobs/available`","requestBody":{"description":"The availability.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"availability":{"type":"string","enum":["online","offline","busy"],"example":"online"}}},"example":{"availability":"online"}}}}}},"/client/logistics/location":{"put":{"operationId":"DeliveryClientController_updateLocation","summary":"Update my location","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/location","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/location","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Reports the driver's position. Called continuously by the driver app while online; it drives proximity dispatch and, on an active job, the customer's tracking map.\n\n#### Signature\n\n```http\nPUT /client/logistics/location (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Visible to customers on an active job.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/logistics/jobs/{jobId}/tracking`","requestBody":{"description":"The location.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number","example":40.7128},"lng":{"type":"number","example":-74.006}}},"example":{"lat":40.7128,"lng":-74.006}}}}}},"/client/logistics/quote":{"post":{"operationId":"DeliveryClientController_getPriceQuote","summary":"Get a delivery quote","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The quote","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/quote","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Prices a delivery from its stops. Fast path — use the validated form when the addresses have not been checked.\n\n#### Signature\n\n```http\nPOST /client/logistics/quote (body) -> The quote\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/quote/validated`","requestBody":{"description":"The stops.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"stops":[{"type":"pickup","location":{"lat":37.7936,"lng":-122.3958}},{"type":"dropoff","location":{"lat":37.7879,"lng":-122.3972}}]}}}}}},"/client/logistics/quote/validated":{"post":{"operationId":"DeliveryClientController_getValidatedPriceQuote","summary":"Get a validated delivery quote","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The validated quote","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/quote/validated","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Geocodes and validates each address before pricing, so an unreachable address is caught here rather than after a driver has been dispatched. Slower than the plain quote, and the one to use before creating an order.\n\n#### Signature\n\n```http\nPOST /client/logistics/quote/validated (body) -> The validated quote\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders`","requestBody":{"description":"The stops.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"stops":[{"type":"pickup","location":{"address":"1 Market St, San Francisco, CA"}},{"type":"dropoff","location":{"address":"500 Howard St, San Francisco, CA"}}]}}}}}},"/client/logistics/jobs":{"post":{"operationId":"DeliveryClientController_createJob","summary":"Create a delivery job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Creates a job as the calling customer. Unpaid — use `POST /client/logistics/orders` for the flow that also sets up payment.\n\n#### Signature\n\n```http\nPOST /client/logistics/jobs (body) -> The job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders`","requestBody":{"description":"The job.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"stops":[{"type":"pickup","location":{"address":"1 Market St, San Francisco, CA"}},{"type":"dropoff","location":{"address":"500 Howard St, San Francisco, CA"}}]}}}}},"get":{"operationId":"DeliveryClientController_getMyJobs","summary":"List my jobs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"in_progress"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"role","required":false,"in":"query","description":"`agent` or `customer`.","schema":{"type":"string"},"example":"agent"}],"responses":{"200":{"description":"Jobs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"The caller's jobs. `role` selects the perspective — as the driver, or as the customer who ordered them — since one account can be both.\n\n#### Signature\n\n```http\nGET /client/logistics/jobs (status?: string, role?: string, page?: integer, pageSize?: integer) -> Jobs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/jobs/available`"}},"/client/logistics/jobs/available":{"get":{"operationId":"DeliveryClientController_getAvailableJobs","summary":"List jobs I can take","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Available jobs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/available","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/jobs/available","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Jobs offered or broadcast that this driver is eligible for, given their zone, vehicle and approval status. Empty while offline.\n\n#### Signature\n\n```http\nGET /client/logistics/jobs/available () -> Available jobs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/logistics/jobs/{jobId}/accept`"}},"/client/logistics/jobs/{jobId}":{"get":{"operationId":"DeliveryClientController_getJob","summary":"Get one of my jobs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"A job the caller is party to — as driver or as customer. Someone else's job is not readable here.\n\n#### Signature\n\n```http\nGET /client/logistics/jobs/{jobId} (jobId: string) -> The job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/jobs/{jobId}/contacts`"}},"/client/logistics/jobs/{jobId}/accept":{"put":{"operationId":"DeliveryClientController_acceptJob","summary":"Accept a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/accept","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/accept","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Job is no longer available — Another driver accepted it first.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Job is no longer available","path":"/client/logistics/jobs/{jobId}/accept","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Takes an available job. Broadcast jobs are first-come — a job someone else already took returns 409, which is expected rather than an error to retry.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/accept (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n| `409` | JOB_UNAVAILABLE | Job is no longer available | Another driver accepted it first. | Pick another job from the available list. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/jobs/available`"}},"/client/logistics/jobs/{jobId}/reject":{"put":{"operationId":"DeliveryClientController_rejectJob","summary":"Reject a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/reject","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/reject","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Declines an offered job, returning it to the pool for other drivers.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/reject (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.","requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Too far from me"}}}}}},"/client/logistics/jobs/{jobId}/start-pickup":{"put":{"operationId":"DeliveryClientController_startPickup","summary":"Start the pickup leg","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/start-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/start-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Marks the driver en route to collect. This is what starts the customer's tracking view for the job.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/start-pickup (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`."}},"/client/logistics/jobs/{jobId}/arrive-pickup":{"put":{"operationId":"DeliveryClientController_arrivePickup","summary":"Arrive at pickup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/arrive-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/arrive-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Marks arrival at the pickup, stamping the time any waiting charge is measured from.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/arrive-pickup (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.","requestBody":{"description":"Optional arrival detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"lat":37.7936,"lng":-122.3958}}}}}},"/client/logistics/jobs/{jobId}/complete-pickup":{"put":{"operationId":"DeliveryClientController_completePickup","summary":"Complete the pickup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/complete-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/complete-pickup","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Records that the goods were collected — custody passes to the driver here. Upload pickup photos first; they are the driver's record of what condition the items were in.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/complete-pickup (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/jobs/{jobId}/images`","requestBody":{"description":"Optional pickup detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"notes":"Two sealed boxes"}}}}}},"/client/logistics/jobs/{jobId}/start-dropoff":{"put":{"operationId":"DeliveryClientController_startDropoff","summary":"Start the dropoff leg","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/start-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/start-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Marks the driver en route to the delivery address.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/start-dropoff (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`."}},"/client/logistics/jobs/{jobId}/arrive-dropoff":{"put":{"operationId":"DeliveryClientController_arriveDropoff","summary":"Arrive at dropoff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/arrive-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/arrive-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Marks arrival at the delivery address.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/arrive-dropoff (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.","requestBody":{"description":"Optional arrival detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"lat":37.7879,"lng":-122.3972}}}}}},"/client/logistics/jobs/{jobId}/complete-dropoff":{"put":{"operationId":"DeliveryClientController_completeDropoff","summary":"Complete the dropoff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/complete-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/complete-dropoff","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Records the handover — who received it, signature, notes. Attach a `proof` image: a delivery disputed later with no proof is generally resolved against the driver.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/complete-dropoff (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/jobs/{jobId}/images`","requestBody":{"description":"Handover detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"receivedBy":"Ada Lovelace","notes":"Left with reception"}}}}}},"/client/logistics/jobs/{jobId}/complete":{"put":{"operationId":"DeliveryClientController_completeJob","summary":"Complete a job","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/complete","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/complete","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Closes the delivery. Driver pay is settled by the operator's payment run, not by this call.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/complete (jobId: string) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/payouts/request`"}},"/client/logistics/jobs/{jobId}/tracking":{"put":{"operationId":"DeliveryClientController_updateJobTracking","summary":"Update job tracking","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/tracking","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/tracking","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Pushes the driver's live position onto the job. This is the feed behind the customer's tracking map, so it is customer-visible.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/tracking (jobId: string, body) -> The updated job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Customer-visible.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/orders/{jobId}/track`","requestBody":{"description":"The position.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lat":{"type":"number","example":37.79},"lng":{"type":"number","example":-122.4},"heading":{"type":"number","example":92},"eta":{"type":"string","format":"date-time"}}},"example":{"lat":37.79,"lng":-122.4,"heading":92}}}}}},"/client/logistics/jobs/{jobId}/issues":{"post":{"operationId":"DeliveryClientController_reportIssue","summary":"Report a job issue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The issue","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/issues","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/issues","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Raises a problem from the caller's side — access refused, damage, a wrong address. Photos attached here are what support the driver's account if the delivery is later disputed.\n\n#### Signature\n\n```http\nPOST /client/logistics/jobs/{jobId}/issues (jobId: string, body) -> The issue\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/jobs/{jobId}/issues`","requestBody":{"description":"The issue.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"reportedBy":{"type":"string","enum":["driver","customer","recipient"],"example":"driver"},"type":{"type":"string","example":"access_denied"},"description":{"type":"string","example":"Gate code did not work"},"photos":{"type":"array","items":{"type":"object","additionalProperties":true}},"location":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}}}}},"example":{"reportedBy":"driver","type":"access_denied","description":"Gate code did not work"}}}}},"get":{"operationId":"DeliveryClientController_getJobIssues","summary":"Get job issues","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"Issues","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/issues","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/issues","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Issues raised on a job the caller is party to.\n\n#### Signature\n\n```http\nGET /client/logistics/jobs/{jobId}/issues (jobId: string) -> Issues\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/jobs/{jobId}/issues`"}},"/client/logistics/jobs/{jobId}/images":{"post":{"operationId":"DeliveryClientController_addJobImages","summary":"Add job images","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/images","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/images","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Attaches uploaded images to a job under a category — `proof` for delivery evidence, `pickup`/`dropoff` for condition, `damage` for a claim. Upload the file through `POST /client/logistics/upload` first; this takes paths, not file data.\n\n#### Signature\n\n```http\nPOST /client/logistics/jobs/{jobId}/images (jobId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/upload`","requestBody":{"description":"The images.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["images","category"],"properties":{"images":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","example":"logistics/jobs/JOB-4821/proof_img.jpg"},"url":{"type":"string","example":"https://cdn.example.com/logistics/jobs/JOB-4821/proof_img.jpg"},"contentType":{"type":"string","example":"image/jpeg"}}}},"category":{"type":"string","enum":["proof","pickup","dropoff","issue","damage","other"],"example":"proof"},"stopIndex":{"type":"integer","example":1}}},"example":{"images":[{"path":"logistics/jobs/JOB-4821/proof_img.jpg","url":"https://cdn.example.com/logistics/jobs/JOB-4821/proof_img.jpg"}],"category":"proof"}}}}},"get":{"operationId":"DeliveryClientController_getJobImages","summary":"Get job images","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"},{"name":"category","required":false,"in":"query","schema":{"type":"string"},"example":"proof"}],"responses":{"200":{"description":"Images","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/images","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/images","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Images attached to a job, optionally by category.\n\n#### Signature\n\n```http\nGET /client/logistics/jobs/{jobId}/images (jobId: string, category?: string) -> Images\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/jobs/{jobId}/images`"}},"/client/logistics/jobs/{jobId}/messages":{"post":{"operationId":"DeliveryClientController_sendMessage","summary":"Send a job message","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Posts a message on the job thread. It reaches the other party — driver to customer or the reverse — so it is outward-facing.\n\n#### Signature\n\n```http\nPOST /client/logistics/jobs/{jobId}/messages (jobId: string, body) -> The message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Reaches the other party.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/jobs/{jobId}/messages`","requestBody":{"description":"The message.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","example":"I'm at the gate, which buzzer?"},"type":{"type":"string","enum":["text","image","location","eta_update","status_update"],"example":"text"},"attachment":{"type":"object","additionalProperties":true}}},"example":{"content":"I'm at the gate, which buzzer?"}}}}},"get":{"operationId":"DeliveryClientController_getMessages","summary":"Get job messages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/messages","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/messages","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"The job's message thread.\n\n#### Signature\n\n```http\nGET /client/logistics/jobs/{jobId}/messages (jobId: string) -> Messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/logistics/jobs/{jobId}/messages/read`"}},"/client/logistics/jobs/{jobId}/messages/read":{"put":{"operationId":"DeliveryClientController_markMessagesRead","summary":"Mark job messages read","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/messages/read","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/messages/read","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Clears the unread state on a job thread for the caller.\n\n#### Signature\n\n```http\nPUT /client/logistics/jobs/{jobId}/messages/read (jobId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/jobs/{jobId}/messages`"}},"/client/logistics/jobs/{jobId}/contacts":{"get":{"operationId":"DeliveryClientController_getJobContacts","summary":"Get job contacts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"Contacts","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/jobs/{jobId}/contacts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/jobs/{jobId}/contacts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"The contact details for the job — pickup and dropoff contacts, and the other party. Contains personal phone numbers, so it is scoped to jobs the caller is actually on.\n\n#### Signature\n\n```http\nGET /client/logistics/jobs/{jobId}/contacts (jobId: string) -> Contacts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Returns personal contact details.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/jobs/{jobId}/messages`"}},"/client/logistics/upload":{"post":{"operationId":"DeliveryClientController_uploadFile","summary":"Upload a file","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The stored file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"No file provided — No `file` part was sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No file provided","path":"/client/logistics/upload","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/upload","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to upload file — Storage rejected the file.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to upload file","path":"/client/logistics/upload","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Logistics · Driver"],"description":"Uploads a file as `multipart/form-data` under the field name `file`, and returns its stored path for attaching to a job.\n\nThe storage path is built from two **form fields sent alongside the file**, not from query parameters: `jobId` and `status`. With a `jobId` the file lands at `logistics/jobs/{jobId}/{status}_{filename}`; without one it goes to `logistics/uploads/{status}_{filename}`, where nothing links it to a delivery. `status` defaults to `general`.\n\n#### Signature\n\n```http\nPOST /client/logistics/upload (body) -> The stored file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Send `jobId` as a form field or the file is orphaned from the delivery.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `400` | NO_FILE | No file provided | No `file` part was sent. | Send multipart form data with a `file` field. |\n| `500` | UPLOAD_FAILED | Failed to upload file | Storage rejected the file. | Retry. |\n\nPlus the standard platform errors: `403`, `429`.\n\n#### See also\n\n- `POST /client/logistics/jobs/{jobId}/images`","requestBody":{"description":"Multipart form: `file`, plus optional `jobId` and `status` fields.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"jobId":{"type":"string","description":"Files without this are not tied to a job.","example":"JOB-4821"},"status":{"type":"string","description":"Filename prefix. Defaults to `general`.","example":"proof"}}}}}}}},"/client/logistics/payout-methods":{"get":{"operationId":"DeliveryClientController_getPayoutMethods","summary":"List my payout methods","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Payout methods","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payout-methods","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/payout-methods","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"How the driver gets paid — bank, PayPal, Venmo, Cash App, debit card or crypto. Account numbers are stored masked; full values are not returned.\n\n#### Signature\n\n```http\nGET /client/logistics/payout-methods () -> Payout methods\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/payout-methods`"},"post":{"operationId":"DeliveryClientController_addPayoutMethod","summary":"Add a payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The payout method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payout-methods","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/payout-methods","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Adds a way for the driver to be paid. `type` selects which sub-object matters — send `bank` for a bank account, `paypal` for a PayPal email, and so on; the others are ignored.\n\n**The body carries bank account and card details.** Never log it, and send it only over TLS. A new method typically starts unverified and is not usable for a payout until it is verified.\n\n`isDefault` makes it the target for payout requests that name no method.\n\n#### Signature\n\n```http\nPOST /client/logistics/payout-methods (body) -> The payout method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sensitive payload — do not log the request body.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/logistics/payout-methods/{methodId}/default`","requestBody":{"description":"The payout method.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["bank","paypal","venmo","cashapp","debit_card","crypto"],"example":"bank"},"label":{"type":"string","example":"Main checking"},"isDefault":{"type":"boolean","example":true},"bank":{"type":"object","properties":{"bankName":{"type":"string","example":"Example Bank"},"accountType":{"type":"string","enum":["checking","savings"],"example":"checking"},"routingNumber":{"type":"string","description":"Sensitive.","example":"021000021"},"accountNumber":{"type":"string","description":"Sensitive.","example":"000123456789"},"accountHolderName":{"type":"string","example":"Ada Lovelace"},"accountHolderType":{"type":"string","enum":["individual","business"],"example":"individual"}}},"debitCard":{"type":"object","properties":{"cardBrand":{"type":"string"},"last4":{"type":"string","example":"4242"},"expirationMonth":{"type":"integer","example":12},"expirationYear":{"type":"integer","example":2029},"cardholderName":{"type":"string"},"token":{"type":"string","description":"Tokenised card reference."}}},"paypal":{"type":"object","properties":{"email":{"type":"string","example":"ada@example.com"}}},"venmo":{"type":"object","properties":{"handle":{"type":"string","example":"@ada"},"phoneNumber":{"type":"string"}}},"cashapp":{"type":"object","properties":{"cashtag":{"type":"string","example":"$ada"},"phoneNumber":{"type":"string"}}},"crypto":{"type":"object","properties":{"currency":{"type":"string","example":"USDC"},"address":{"type":"string"},"network":{"type":"string","example":"ethereum"}}}}},"examples":{"bank":{"summary":"Bank account","value":{"type":"bank","label":"Main checking","isDefault":true,"bank":{"bankName":"Example Bank","accountType":"checking","routingNumber":"021000021","accountNumber":"000123456789","accountHolderName":"Ada Lovelace","accountHolderType":"individual"}}},"paypal":{"summary":"PayPal","value":{"type":"paypal","paypal":{"email":"ada@example.com"}}}}}}}}},"/client/logistics/payout-methods/{methodId}":{"put":{"operationId":"DeliveryClientController_updatePayoutMethod","summary":"Update a payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id.","example":"PM-4821"}],"responses":{"200":{"description":"The updated method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payout-methods/{methodId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Changes a method's label, default flag or status. Account details themselves are not editable — replace the method instead.\n\n#### Signature\n\n```http\nPUT /client/logistics/payout-methods/{methodId} (methodId: string, body) -> The updated method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/logistics/payout-methods/{methodId}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","example":"Main checking"},"isDefault":{"type":"boolean","example":true},"status":{"type":"string","enum":["pending","verified","disabled"],"example":"verified"}}},"example":{"label":"Main checking"}}}}},"delete":{"operationId":"DeliveryClientController_removePayoutMethod","summary":"Remove a payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id.","example":"PM-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"success":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payout-methods/{methodId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Deletes a payout method. Removing the default leaves the driver with no default — a payout request that names no method then has nowhere to go, so set another default first.\n\n#### Signature\n\n```http\nDELETE /client/logistics/payout-methods/{methodId} (methodId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Set another default before removing the current one.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/logistics/payout-methods/{methodId}/default`"}},"/client/logistics/payout-methods/{methodId}/default":{"put":{"operationId":"DeliveryClientController_setDefaultPayoutMethod","summary":"Set the default payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id.","example":"PM-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payout-methods/{methodId}/default","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Makes one method the default. Payout requests without an explicit `methodId` are paid to it.\n\n#### Signature\n\n```http\nPUT /client/logistics/payout-methods/{methodId}/default (methodId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/payouts/request`"}},"/client/logistics/payouts":{"get":{"operationId":"DeliveryClientController_getMyPayouts","summary":"List my payouts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"pending"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Payouts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/payouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"The driver's payout history and their states.\n\n#### Signature\n\n```http\nGET /client/logistics/payouts (status?: string, page?: integer, pageSize?: integer) -> Payouts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/payouts/request`"}},"/client/logistics/payouts/request":{"post":{"operationId":"DeliveryClientController_requestPayout","summary":"Request a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The payout request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid amount — The amount is missing, non-positive or above the available balance.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid amount","path":"/client/logistics/payouts/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payouts/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Agent profile not found. Please register first. — The caller is not a registered delivery agent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Agent profile not found. Please register first.","path":"/client/logistics/payouts/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Asks for accumulated earnings to be paid out, to `methodId` or to the default method. The request enters a pending state for the operator to process — this does not itself move money.\n\n#### Signature\n\n```http\nPOST /client/logistics/payouts/request (body) -> The payout request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Creates a request; the operator settles it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | NO_AGENT_PROFILE | Agent profile not found. Please register first. | The caller is not a registered delivery agent. | Register with `POST /client/logistics/register`. |\n| `400` | INVALID_AMOUNT | Invalid amount | The amount is missing, non-positive or above the available balance. | Check available earnings first. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/payouts`","requestBody":{"description":"The request.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","example":12500},"methodId":{"type":"string","description":"Defaults to the default payout method.","example":"PM-4821"},"notes":{"type":"string"}}},"example":{"amount":12500}}}}}},"/client/logistics/payments/{jobId}":{"get":{"operationId":"DeliveryClientController_getJobPayment","summary":"Get a job's payment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"},{"name":"includeTransaction","required":false,"in":"query","description":"Include the full transaction record.","schema":{"type":"boolean"},"example":false}],"responses":{"200":{"description":"The payment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payments/{jobId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/payments/{jobId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"What was charged and paid on one job. `includeTransaction=true` adds the full underlying transaction record, which is more detail than a normal client view needs.\n\n#### Signature\n\n```http\nGET /client/logistics/payments/{jobId} (jobId: string, includeTransaction?: boolean) -> The payment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/payments`"}},"/client/logistics/payments":{"get":{"operationId":"DeliveryClientController_getPaymentHistory","summary":"List my payments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"completed"}],"responses":{"200":{"description":"Payments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/payments","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Driver"],"description":"Payments across the caller's jobs.\n\n#### Signature\n\n```http\nGET /client/logistics/payments (status?: string, page?: integer, pageSize?: integer) -> Payments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/payments/{jobId}`"}},"/client/logistics/stripe/config":{"get":{"operationId":"DeliveryClientController_getStripeConfig","summary":"Get the Stripe client configuration","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Stripe client config","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/stripe/config","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"The publishable key and options a client needs to mount Stripe. Publishable values only — no secret key is exposed here.\n\n#### Signature\n\n```http\nGET /client/logistics/stripe/config () -> Stripe client config\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/stripe/intent`"}},"/client/logistics/stripe/intent":{"post":{"operationId":"DeliveryClientController_stripePaymentIntent","summary":"Create a Stripe payment intent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The intent and client secret","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/stripe/intent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Creates a payment intent and returns its client secret for the browser to confirm. The confirmation happens client-side against Stripe; the server learns the outcome through `stripe/verify` or the order completion endpoint.\n\n#### Signature\n\n```http\nPOST /client/logistics/stripe/intent (body) -> The intent and client secret\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/stripe/verify`","requestBody":{"description":"What to charge.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"jobId":"JOB-4821","amount":3200}}}}}},"/client/logistics/stripe/verify":{"post":{"operationId":"DeliveryClientController_verifyStripePayment","summary":"Verify a Stripe payment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The verification result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/stripe/verify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Confirms with Stripe that a payment intent actually succeeded, rather than trusting the browser's word for it. This is the check that stops a client claiming an unpaid delivery was paid.\n\n#### Signature\n\n```http\nPOST /client/logistics/stripe/verify (body) -> The verification result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Server-side verification — do not skip it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders/{jobId}/complete-payment`","requestBody":{"description":"The intent to verify.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"paymentIntentId":"pi_3Abc123"}}}}}},"/client/logistics/paypal/create":{"post":{"operationId":"DeliveryClientController_createPayPalOrder","summary":"Create a PayPal order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The PayPal order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/paypal/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Creates a PayPal order for the customer to approve in their browser.\n\n#### Signature\n\n```http\nPOST /client/logistics/paypal/create (body) -> The PayPal order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/paypal/capture`","requestBody":{"description":"What to charge.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"jobId":"JOB-4821","amount":3200}}}}}},"/client/logistics/paypal/capture":{"post":{"operationId":"DeliveryClientController_capturePayPalOrder","summary":"Capture a PayPal order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The capture result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/paypal/capture","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Captures an approved PayPal order — this is the point the money actually moves. Approval alone does not charge the customer.\n\n#### Signature\n\n```http\nPOST /client/logistics/paypal/capture (body) -> The capture result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Charges the customer.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/paypal/create`","requestBody":{"description":"The order to capture.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"paypalOrderId":"5O190127TN364715T"}}}}}},"/client/logistics/orders":{"post":{"operationId":"DeliveryClientController_createDeliveryOrder","summary":"Create a delivery order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The order and payment setup","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"The customer-facing order flow: creates the job and starts payment with the chosen provider in one call. Each stop carries a location, a contact and the items being moved.\n\nCreating the order does **not** complete payment — the client must confirm with Stripe or PayPal and then call `complete-payment`. Until that happens the delivery is not paid for.\n\n#### Signature\n\n```http\nPOST /client/logistics/orders (body) -> The order and payment setup\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Payment must still be confirmed and completed.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders/{jobId}/complete-payment`","requestBody":{"description":"The order.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["stops","paymentMethod"],"properties":{"stops":{"type":"array","items":{"type":"object","required":["type","location"],"properties":{"type":{"type":"string","enum":["pickup","dropoff"],"example":"pickup"},"location":{"type":"object","properties":{"lat":{"type":"number","example":37.7936},"lng":{"type":"number","example":-122.3958},"address":{"type":"object","additionalProperties":true},"placeName":{"type":"string","example":"Ferry Building"}}},"contact":{"type":"object","properties":{"name":{"type":"string","example":"Ada Lovelace"},"phone":{"type":"string","example":"+15551234567"}}},"instructions":{"type":"string","example":"Ring the bell twice"},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","example":"Sealed box"},"quantity":{"type":"integer","example":2},"weight":{"type":"number","example":4.5}}}}}}},"notes":{"type":"string"},"images":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"url":{"type":"string"}}}},"paymentMethod":{"type":"string","enum":["stripe","paypal"],"example":"stripe"}}},"example":{"stops":[{"type":"pickup","location":{"lat":37.7936,"lng":-122.3958},"contact":{"name":"Ada Lovelace","phone":"+15551234567"},"items":[{"description":"Sealed box","quantity":2}]},{"type":"dropoff","location":{"lat":37.7879,"lng":-122.3972},"contact":{"name":"Grace Hopper","phone":"+15559876543"},"instructions":"Leave with reception"}],"paymentMethod":"stripe"}}}}},"get":{"operationId":"DeliveryClientController_getMyDeliveryOrders","summary":"List my orders","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"in_progress"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Orders","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Deliveries the caller ordered, with their status.\n\n#### Signature\n\n```http\nGET /client/logistics/orders (status?: string, page?: integer, pageSize?: integer) -> Orders\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/orders/{jobId}/track`"}},"/client/logistics/orders/{jobId}/complete-payment":{"post":{"operationId":"DeliveryClientController_completeDeliveryPayment","summary":"Complete an order payment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The payment result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}/complete-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}/complete-payment","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Finalises payment after the customer confirmed with Stripe or approved with PayPal. Pass the provider reference — `paymentIntentId` for Stripe, `paypalOrderId` for PayPal — which the server verifies against the provider rather than taking on trust.\n\n#### Signature\n\n```http\nPOST /client/logistics/orders/{jobId}/complete-payment (jobId: string, body) -> The payment result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders/{jobId}/payment-intent`","requestBody":{"description":"The provider confirmation.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentMethod"],"properties":{"paymentMethod":{"type":"string","enum":["stripe","paypal"],"example":"stripe"},"paymentIntentId":{"type":"string","description":"Stripe.","example":"pi_3Abc123"},"paypalOrderId":{"type":"string","description":"PayPal.","example":"5O190127TN364715T"}}},"example":{"paymentMethod":"stripe","paymentIntentId":"pi_3Abc123"}}}}}},"/client/logistics/orders/{jobId}/payment-intent":{"post":{"operationId":"DeliveryClientController_createOrderPaymentIntent","summary":"Create a payment intent for an order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The intent","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}/payment-intent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}/payment-intent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Creates a fresh payment intent for an existing order — for retrying after a failed or abandoned attempt without re-creating the delivery.\n\n#### Signature\n\n```http\nPOST /client/logistics/orders/{jobId}/payment-intent (jobId: string) -> The intent\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders/{jobId}/complete-payment`"}},"/client/logistics/orders/{jobId}/cancel":{"put":{"operationId":"DeliveryClientController_cancelDeliveryOrder","summary":"Cancel an order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The cancelled order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to cancel this job — The job is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to cancel this job","path":"/client/logistics/orders/{jobId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}/cancel","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Cancels the caller's delivery. Whether anything is refunded depends on the org's cancellation policy and how far the job has progressed — a cancellation after pickup usually is not free.\n\n#### Signature\n\n```http\nPUT /client/logistics/orders/{jobId}/cancel (jobId: string, body) -> The cancelled order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A cancellation fee may apply.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n| `403` | NOT_AUTHORIZED_CANCEL | Not authorized to cancel this job | The job is not the caller's. | Only the ordering customer can cancel here. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/logistics/orders`","requestBody":{"description":"Why.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"No longer needed"}}}}}},"/client/logistics/orders/{jobId}":{"put":{"operationId":"DeliveryClientController_updateDeliveryOrder","summary":"Update an order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"The updated order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Changes an order before a driver is on the way — addresses, contacts, instructions. Once the pickup leg has started the details are in the driver's hands and changes here may not reach them; message the driver instead.\n\n#### Signature\n\n```http\nPUT /client/logistics/orders/{jobId} (jobId: string, body) -> The updated order\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Late changes may not reach the driver.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/jobs/{jobId}/messages`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"notes":"Buzzer 4B, not 4A"}}}}}},"/client/logistics/orders/{jobId}/track":{"get":{"operationId":"DeliveryClientController_trackDeliveryOrder","summary":"Track an order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"200":{"description":"Tracking state","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}/track","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}/track","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Live status and driver position for a delivery — what a customer's tracking screen polls.\n\n#### Signature\n\n```http\nGET /client/logistics/orders/{jobId}/track (jobId: string) -> Tracking state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/logistics/jobs/{jobId}/tracking`"}},"/client/logistics/orders/{jobId}/rate":{"post":{"operationId":"DeliveryClientController_rateDelivery","summary":"Rate a delivery","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The rating","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Can only rate completed deliveries — The delivery is not finished.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Can only rate completed deliveries","path":"/client/logistics/orders/{jobId}/rate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}/rate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to rate this delivery — The caller did not order it.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to rate this delivery","path":"/client/logistics/orders/{jobId}/rate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}/rate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Rates a completed delivery. Only completed deliveries can be rated, and only by the customer who ordered it. Ratings feed the driver's performance score, which affects the jobs they are offered — so this is consequential for the driver, not just feedback.\n\n#### Signature\n\n```http\nPOST /client/logistics/orders/{jobId}/rate (jobId: string, body) -> The rating\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Affects the driver's standing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n| `400` | NOT_COMPLETED | Can only rate completed deliveries | The delivery is not finished. | Wait until it completes. |\n| `403` | NOT_AUTHORIZED_RATE | Not authorized to rate this delivery | The caller did not order it. | Only the ordering customer can rate. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders/{jobId}/tip`","requestBody":{"description":"The rating.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["rating"],"properties":{"rating":{"type":"number","description":"Typically 1–5.","example":5},"comment":{"type":"string","example":"Fast and careful."}}},"example":{"rating":5,"comment":"Fast and careful."}}}}}},"/client/logistics/orders/{jobId}/tip":{"post":{"operationId":"DeliveryClientController_addTip","summary":"Tip a driver","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The tip payment setup","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Can only tip on completed deliveries — The delivery is not finished.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Can only tip on completed deliveries","path":"/client/logistics/orders/{jobId}/tip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}/tip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}/tip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Starts a tip on a completed delivery, returning a payment intent or PayPal order to confirm. The tip is not charged until `tip/complete` runs.\n\n#### Signature\n\n```http\nPOST /client/logistics/orders/{jobId}/tip (jobId: string, body) -> The tip payment setup\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n| `400` | NOT_COMPLETED | Can only tip on completed deliveries | The delivery is not finished. | Wait until it completes. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders/{jobId}/tip/complete`","requestBody":{"description":"The tip.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","example":500},"paymentMethod":{"type":"string","enum":["stripe","paypal"],"example":"stripe"}}},"example":{"amount":500,"paymentMethod":"stripe"}}}}}},"/client/logistics/orders/{jobId}/tip/complete":{"post":{"operationId":"DeliveryClientController_completeTip","summary":"Complete a tip payment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"jobId","required":true,"in":"path","schema":{"type":"string"},"description":"Delivery job id.","example":"JOB-4821"}],"responses":{"201":{"description":"The tip result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No customer or user could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/logistics/orders/{jobId}/tip/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Delivery job not found — No job has that id, or it is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Delivery job not found","path":"/client/logistics/orders/{jobId}/tip/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Logistics · Customer"],"description":"Finalises a tip after the customer confirmed with the provider. This is where the tip is actually charged and credited to the driver — without it the tip was only ever intended.\n\n#### Signature\n\n```http\nPOST /client/logistics/orders/{jobId}/tip/complete (jobId: string, body) -> The tip result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Charges the customer and credits the driver.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No customer or user could be resolved from the token. | Sign in. |\n| `404` | JOB_NOT_FOUND | Delivery job not found | No job has that id, or it is not the caller's. | List your jobs with `GET /client/logistics/jobs`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/logistics/orders/{jobId}/tip`","requestBody":{"description":"The confirmation.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","example":500},"paymentIntentId":{"type":"string","description":"Stripe.","example":"pi_3Abc123"},"paypalOrderId":{"type":"string","description":"PayPal.","example":"5O190127TN364715T"}}},"example":{"amount":500,"paymentIntentId":"pi_3Abc123"}}}}}},"/finance/wallets":{"get":{"operationId":"PayoutController_listWallets","summary":"List wallets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ownerType","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by owner kind.","example":"host"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"description":"Page number. Values below 1 are clamped to 1.","example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":100},"description":"Page size. Clamped to 1–500; a larger value is silently reduced.","example":100}],"responses":{"200":{"description":"A page of wallets","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}},"total":{"type":"integer","example":84}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Lists wallets with optional filters and paging.\n\n#### Signature\n\n```http\nGET /finance/wallets (ownerType?: string, status?: string, page?: integer, pageSize?: integer) -> A page of wallets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- `pageSize` is clamped to a maximum of 500 without warning.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/wallets/owner/lookup`"},"post":{"operationId":"PayoutController_createWallet","summary":"Create a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Creates a wallet for an owner. Nothing prevents a second wallet for the same owner, so use `POST /finance/wallets/get-or-create` unless you specifically want a duplicate.\n\n#### Signature\n\n```http\nPOST /finance/wallets (body) -> The created wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- No uniqueness check on the owner — a duplicate wallet splits their balance across two records.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/get-or-create`","requestBody":{"description":"Who the wallet belongs to.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["ownerType","ownerId"],"properties":{"ownerType":{"type":"string","description":"Kind of owner — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","example":"cus_4821"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","default":"USD","example":"USD"},"name":{"type":"string","example":"Ada Lovelace"}}},"example":{"ownerType":"host","ownerId":"cus_4821","email":"ada@example.com","name":"Ada Lovelace","currency":"USD"}}}}}},"/finance/wallets/{walletId}":{"get":{"operationId":"PayoutController_getWallet","summary":"Get a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"200":{"description":"The wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Fetches one wallet with its balances, holds and payout settings.\n\n#### Signature\n\n```http\nGET /finance/wallets/{walletId} (walletId: string) -> The wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- `balance` includes held funds; `availableBalance` is what can actually be withdrawn.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/wallets/transactions/lookup`"}},"/finance/wallets/transactions/lookup":{"get":{"operationId":"PayoutController_getWalletWithTransactions","summary":"Get a wallet with its transactions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":false,"in":"query","schema":{"type":"string"},"description":"Wallet id. Send this or `customerId`.","example":"WAL-4821"},{"name":"customerId","required":false,"in":"query","schema":{"type":"string"},"description":"Customer id. Send this or `walletId`.","example":"cus_4821"}],"responses":{"200":{"description":"The wallet and its transactions, or an error object when no identifier was given","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"ok":{"summary":"Wallet with transactions","value":{"wallet":{"data":{"walletId":"WAL-4821","balance":1250}},"transactions":[]}},"noIdentifier":{"summary":"Neither identifier supplied — still a 200","value":{"error":"Must provide walletId or customerId"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Returns a wallet together with its movement history. Identify it by `walletId` or by `customerId`.\n\n**Supplying neither returns `{ \"error\": \"Must provide walletId or customerId\" }` with a `200`**, not a `400` — check the body rather than the status.\n\n#### Signature\n\n```http\nGET /finance/wallets/transactions/lookup (walletId?: string, customerId?: string) -> The wallet and its transactions, or an error object when no identifier was given\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Validation failure is reported in the body with a `200`, not as a `400`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/wallets/{walletId}`"}},"/finance/wallets/owner/lookup":{"get":{"operationId":"PayoutController_getWalletByOwner","summary":"Get a wallet by owner","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ownerType","required":true,"in":"query","schema":{"type":"string"},"description":"Kind of owner.","example":"host"},{"name":"ownerId","required":true,"in":"query","schema":{"type":"string"},"description":"Identifier within that type.","example":"cus_4821"}],"responses":{"200":{"description":"The owner's wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Finds a wallet by who owns it, rather than by wallet id. The lookup to use when you know the host or driver but not their wallet.\n\n#### Signature\n\n```http\nGET /finance/wallets/owner/lookup (ownerType?: string, ownerId?: string) -> The owner's wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Use `POST /finance/wallets/get-or-create` when the wallet may not exist yet.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/get-or-create`"}},"/finance/wallets/get-or-create":{"post":{"operationId":"PayoutController_getOrCreateWallet","summary":"Get or create a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The existing or newly created wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Returns the owner's existing wallet, creating one if they have none. **Idempotent**, which makes it the safe default: use it wherever a wallet is needed as a side effect of some other operation, rather than checking and then creating.\n\n#### Signature\n\n```http\nPOST /finance/wallets/get-or-create (body) -> The existing or newly created wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Prefer this over `POST /finance/wallets` — it cannot produce a duplicate.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets`","requestBody":{"description":"Who the wallet belongs to.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["ownerType","ownerId"],"properties":{"ownerType":{"type":"string","example":"host"},"ownerId":{"type":"string","example":"cus_4821"},"email":{"type":"string","example":"ada@example.com"},"name":{"type":"string","example":"Ada Lovelace"}}},"example":{"ownerType":"host","ownerId":"cus_4821","email":"ada@example.com","name":"Ada Lovelace"}}}}}},"/finance/wallets/{walletId}/credit":{"post":{"operationId":"PayoutController_creditWallet","summary":"Credit a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"201":{"description":"The updated wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"400":{"description":"Wallet is not active — The wallet is frozen or otherwise not active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Wallet is not active","path":"/finance/wallets/{walletId}/credit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/credit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Adds money to a wallet — an earning, a commission, a bonus, a correction. The `type` classifies the movement for reporting, and `reference` links it to whatever generated it.\n\nThe wallet must be **active**: a frozen wallet accepts nothing, in either direction.\n\n#### Signature\n\n```http\nPOST /finance/wallets/{walletId}/credit (walletId: string, body) -> The updated wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Not idempotent — a retry credits again. Use a unique `reference` and check the history before retrying.\n- Amounts here are in major units (dollars), unlike the payment endpoints which use cents.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n| `400` | — | Wallet is not active | The wallet is frozen or otherwise not active. | Unfreeze it with `POST /finance/wallets/{walletId}/unfreeze`. A frozen wallet accepts no movement in either direction. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/debit`","requestBody":{"description":"What to add, and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Amount to add, in major units.","example":250},"type":{"type":"string","description":"Classification for reporting, e.g. `earning`, `commission`, `adjustment`.","example":"earning"},"reference":{"type":"string","description":"What generated it — an order or booking id.","example":"STW-4821"},"description":{"type":"string","example":"Host earnings for booking STW-4821"}}},"example":{"amount":250,"type":"earning","reference":"STW-4821","description":"Host earnings for booking STW-4821"}}}}}},"/finance/wallets/{walletId}/debit":{"post":{"operationId":"PayoutController_debitWallet","summary":"Debit a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"201":{"description":"The updated wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"400":{"description":"Wallet is not active — The wallet is frozen or otherwise not active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Wallet is not active","path":"/finance/wallets/{walletId}/debit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/debit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Takes money out of a wallet — a fee, a clawback, a correction. Refused when the balance is insufficient, so a wallet cannot go negative through this route.\n\n#### Signature\n\n```http\nPOST /finance/wallets/{walletId}/debit (walletId: string, body) -> The updated wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Debits are checked against `balance`, not `availableBalance` — held funds are not protected from a debit.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n| `400` | — | Wallet is not active | The wallet is frozen or otherwise not active. | Unfreeze it with `POST /finance/wallets/{walletId}/unfreeze`. A frozen wallet accepts no movement in either direction. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/credit`","requestBody":{"description":"What to remove, and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","example":50},"type":{"type":"string","description":"Classification, e.g. `fee`, `clawback`, `adjustment`.","example":"fee"},"reference":{"type":"string","example":"INV-4821"},"description":{"type":"string","example":"Platform fee"}}},"example":{"amount":50,"type":"fee","reference":"INV-4821","description":"Platform fee"}}}}}},"/finance/wallets/{walletId}/hold":{"post":{"operationId":"PayoutController_holdFunds","summary":"Hold funds in a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"201":{"description":"The updated wallet, with the hold appended","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"400":{"description":"Insufficient balance for hold — The hold exceeds what is available.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Insufficient balance for hold","path":"/finance/wallets/{walletId}/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/hold","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Makes part of a balance unavailable without removing it — for a dispute, a chargeback, a security review or a deposit. The money stays in `balance` but comes out of `availableBalance`, so it cannot be paid out while the hold stands.\n\nSet `expiresAt` for a hold that should lapse on its own.\n\n#### Signature\n\n```http\nPOST /finance/wallets/{walletId}/hold (walletId: string, body) -> The updated wallet, with the hold appended\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Holds are stored positionally, and released by index — read the wallet to find the right one before releasing.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n| `400` | INSUFFICIENT_BALANCE | Insufficient balance for hold | The hold exceeds what is available. | Check `availableBalance` before holding. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/release/{holdIndex}`","requestBody":{"description":"What to hold, and why.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason","amount"],"properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"description":"Why the funds are held.","example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","description":"What the hold relates to.","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time","description":"When the hold should lapse.","example":"2026-10-01T00:00:00.000Z"},"notes":{"type":"string","example":"Customer disputed the booking"}}},"example":{"reason":"dispute","amount":250,"reference":"RMA-4821","notes":"Customer disputed the booking"}}}}}},"/finance/wallets/{walletId}/release/{holdIndex}":{"post":{"operationId":"PayoutController_releaseFunds","summary":"Release held funds","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"},{"name":"holdIndex","required":true,"in":"path","schema":{"type":"integer"},"description":"Zero-based index into the wallet's `holds` array.","example":0}],"responses":{"201":{"description":"The updated wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/release/{holdIndex}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Removes a hold and returns the amount to `availableBalance`.\n\n**The hold is identified by its position in the `holds` array, not by an id.** Read the wallet immediately before releasing and use the current index: releasing one hold shifts the positions of those after it, so a stale index releases the wrong hold.\n\n#### Signature\n\n```http\nPOST /finance/wallets/{walletId}/release/{holdIndex} (walletId: string, holdIndex: integer) -> The updated wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Positional identification makes this unsafe to run concurrently against one wallet — two simultaneous releases can target the wrong holds.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/hold`"}},"/finance/wallets/{walletId}/freeze":{"post":{"operationId":"PayoutController_freezeWallet","summary":"Freeze a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"201":{"description":"The frozen wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/freeze","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Suspends a wallet completely. While frozen it accepts no credits, no debits and no payouts — the heavier-handed alternative to holding a specific amount.\n\nSet `unfreezeAt` for a freeze that should lift on its own.\n\n#### Signature\n\n```http\nPOST /finance/wallets/{walletId}/freeze (walletId: string, body) -> The frozen wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Freezing blocks credits too — earnings cannot be recorded while the wallet is frozen. Prefer a hold when only outgoing money should stop.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/unfreeze`\n- `POST /finance/wallets/{walletId}/hold`","requestBody":{"description":"Why the wallet is being frozen.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason","frozenBy"],"properties":{"reason":{"type":"string","example":"Suspected fraudulent activity"},"frozenBy":{"type":"string","description":"Who froze it, for the audit trail.","example":"risk@appmint.io"},"unfreezeAt":{"type":"string","format":"date-time","description":"When it should unfreeze automatically.","example":"2026-10-01T00:00:00.000Z"}}},"example":{"reason":"Suspected fraudulent activity","frozenBy":"risk@appmint.io"}}}}}},"/finance/wallets/{walletId}/unfreeze":{"post":{"operationId":"PayoutController_unfreezeWallet","summary":"Unfreeze a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"201":{"description":"The reactivated wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"400":{"description":"Wallet is not frozen — The wallet is already active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Wallet is not frozen","path":"/finance/wallets/{walletId}/unfreeze","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/unfreeze","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Returns a frozen wallet to active. Only a frozen wallet can be unfrozen — calling this on an active one is an error rather than a no-op.\n\n#### Signature\n\n```http\nPOST /finance/wallets/{walletId}/unfreeze (walletId: string) -> The reactivated wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n| `400` | — | Wallet is not frozen | The wallet is already active. | Read the wallet status first — this is not idempotent. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/freeze`"}},"/finance/wallets/{walletId}/payout-settings":{"put":{"operationId":"PayoutController_updatePayoutSettings","summary":"Update payout settings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"200":{"description":"The updated wallet","content":{"application/json":{"schema":{"type":"object","description":"A wallet (`finance_wallet`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"walletId":{"type":"string","example":"WAL-4821"},"ownerType":{"type":"string","description":"What kind of party owns it — `host`, `driver`, `affiliate`, `customer`.","example":"host"},"ownerId":{"type":"string","description":"Identifier of the owner within that type.","example":"cus_4821"},"name":{"type":"string","example":"Ada Lovelace"},"email":{"type":"string","example":"ada@example.com"},"currency":{"type":"string","example":"USD"},"balance":{"type":"number","description":"Total held, including any amount under hold.","example":1250},"availableBalance":{"type":"number","description":"Balance minus active holds — what can actually be paid out.","example":1000},"status":{"type":"string","description":"Only an `active` wallet accepts credits, debits or payouts.","example":"active"},"holds":{"type":"array","description":"Active holds. **Positional** — a hold is released by its index in this array.","items":{"type":"object","properties":{"reason":{"type":"string","enum":["dispute","chargeback","security","deposit","pending_review","other"],"example":"dispute"},"amount":{"type":"number","example":250},"reference":{"type":"string","example":"RMA-4821"},"expiresAt":{"type":"string","format":"date-time"},"notes":{"type":"string"}}}},"payoutSettings":{"type":"object","additionalProperties":true,"description":"Limits and payout methods for this wallet.","properties":{"minPayout":{"type":"number","example":25},"maxPayout":{"type":"number","example":5000},"dailyLimit":{"type":"number","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Configured payout destinations."}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/payout-settings","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Sets the limits and destinations for a wallet's payouts — minimum, maximum, daily cap, and the payout methods available.\n\nThese are exactly the rules `request-payout` enforces, so a payout rejected for being below the minimum is governed here.\n\n#### Signature\n\n```http\nPUT /finance/wallets/{walletId}/payout-settings (walletId: string, body) -> The updated wallet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/request-payout`","requestBody":{"description":"The settings to apply.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"minPayout":{"type":"number","description":"Smallest payout allowed.","example":25},"maxPayout":{"type":"number","description":"Largest single payout allowed.","example":5000},"dailyLimit":{"type":"number","description":"Total allowed per day.","example":10000},"methods":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Payout destinations. A disabled method is rejected at request time."}}},"example":{"minPayout":25,"maxPayout":5000,"dailyLimit":10000}}}}}},"/finance/payouts":{"get":{"operationId":"PayoutController_listPayouts","summary":"List payouts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"recipientType","required":false,"in":"query","schema":{"type":"string"},"example":"host"},{"name":"recipientId","required":false,"in":"query","schema":{"type":"string"},"example":"cus_4821"},{"name":"walletId","required":false,"in":"query","schema":{"type":"string"},"example":"WAL-4821"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"]},"example":"pending"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"description":"Page number. Values below 1 are clamped to 1.","example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":100},"description":"Page size. Clamped to 1–500; a larger value is silently reduced.","example":100}],"responses":{"200":{"description":"A page of payouts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}},"total":{"type":"integer","example":37}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Lists payouts with optional filters and paging. Filter on `status=pending` for the approval queue.\n\n#### Signature\n\n```http\nGET /finance/payouts (recipientType?: string, recipientId?: string, walletId?: string, status?: string, page?: integer, pageSize?: integer) -> A page of payouts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/payouts/{payoutId}`"}},"/finance/wallets/{walletId}/payout-summary":{"get":{"operationId":"PayoutController_getWalletPayoutSummary","summary":"Get a wallet's payout summary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"200":{"description":"Payout totals and counts by status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/payout-summary","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Totals and counts by status for one wallet — how much has been paid out, how much is pending, how much failed. The figures behind a host's earnings page.\n\n#### Signature\n\n```http\nGET /finance/wallets/{walletId}/payout-summary (walletId: string) -> Payout totals and counts by status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/stats/payouts`"}},"/finance/payouts/{payoutId}":{"get":{"operationId":"PayoutController_getPayout","summary":"Get a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"200":{"description":"The payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/finance/payouts/{payoutId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Fetches one payout by id, with its status and any external reference.\n\n#### Signature\n\n```http\nGET /finance/payouts/{payoutId} (payoutId: string) -> The payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout not found | No payout in the org has that id. | List payouts with `GET /finance/payouts`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/payouts/number/lookup`"}},"/finance/payouts/number/lookup":{"get":{"operationId":"PayoutController_getPayoutByNumber","summary":"Get a payout by number","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutNumber","required":true,"in":"query","schema":{"type":"string"},"description":"The payout number.","example":"PAY-2026-00412"}],"responses":{"200":{"description":"The payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Finds a payout by its human-readable number rather than its id — the number that appears on a remittance advice or a support ticket.\n\n#### Signature\n\n```http\nGET /finance/payouts/number/lookup (payoutNumber?: string) -> The payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/payouts/{payoutId}`"}},"/finance/wallets/{walletId}/request-payout":{"post":{"operationId":"PayoutController_requestPayout","summary":"Request a payout from a wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"walletId","required":true,"in":"path","schema":{"type":"string"},"description":"Wallet id.","example":"WAL-4821"}],"responses":{"201":{"description":"The created payout, pending approval","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Wallet is not active — The wallet is frozen or otherwise not active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Wallet is not active","path":"/finance/wallets/{walletId}/request-payout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — No wallet in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/finance/wallets/{walletId}/request-payout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Creates a payout request against a wallet's available balance. The payout starts `pending` and must be approved, processed and completed before money actually moves.\n\nEvery rule on the wallet is checked here, and each has its own error: the wallet must be active, the amount must be within `availableBalance`, above `minPayout`, below `maxPayout`, inside the daily limit, and a payout method must exist and be enabled.\n\n#### Signature\n\n```http\nPOST /finance/wallets/{walletId}/request-payout (walletId: string, body) -> The created payout, pending approval\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Requesting does not move money. The payout must go through approve → process → complete.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Wallet not found | No wallet in the org has that id. | List wallets with `GET /finance/wallets`, or look one up by owner. |\n| `400` | — | Wallet is not active | The wallet is frozen or otherwise not active. | Unfreeze it with `POST /finance/wallets/{walletId}/unfreeze`. A frozen wallet accepts no movement in either direction. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payouts/{payoutId}/approve`\n- `PUT /finance/wallets/{walletId}/payout-settings`","requestBody":{"description":"How much to pay out, and how.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Amount to withdraw, within `availableBalance`.","example":500},"method":{"type":"string","description":"Which configured payout method to use. Defaults to the wallet's default.","example":"bank_transfer"},"notes":{"type":"string","example":"Monthly settlement"}}},"example":{"amount":500,"method":"bank_transfer","notes":"Monthly settlement"}}}}}},"/finance/payouts/{payoutId}/approve":{"post":{"operationId":"PayoutController_approvePayout","summary":"Approve a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"201":{"description":"The approved payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Payout is not pending — The payout has already been approved, processed, or closed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Payout is not pending","path":"/finance/payouts/{payoutId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/finance/payouts/{payoutId}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Authorises a pending payout for processing. Only a `pending` payout can be approved — this is the control point before money leaves.\n\n`approvedBy` is recorded, so the audit trail names who authorised it.\n\n#### Signature\n\n```http\nPOST /finance/payouts/{payoutId}/approve (payoutId: string, body) -> The approved payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout not found | No payout in the org has that id. | List payouts with `GET /finance/payouts`. |\n| `400` | NOT_PENDING | Payout is not pending | The payout has already been approved, processed, or closed. | Read the payout first — approval is not idempotent. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payouts/{payoutId}/process`","requestBody":{"description":"Who approved it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["approvedBy"],"properties":{"approvedBy":{"type":"string","description":"Recorded on the payout.","example":"finance@appmint.io"},"notes":{"type":"string","example":"Verified against August bookings"}}},"example":{"approvedBy":"finance@appmint.io","notes":"Verified against August bookings"}}}}}},"/finance/payouts/{payoutId}/process":{"post":{"operationId":"PayoutController_processPayout","summary":"Process a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"201":{"description":"The processing payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Payout cannot be processed — The payout is not in a state that allows processing — typically it has not been approved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Payout cannot be processed","path":"/finance/payouts/{payoutId}/process","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/finance/payouts/{payoutId}/process","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Moves an approved payout into `processing` — it has been handed to whatever actually transfers the money. Mark the outcome with `complete` or `fail`.\n\n#### Signature\n\n```http\nPOST /finance/payouts/{payoutId}/process (payoutId: string) -> The processing payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout not found | No payout in the org has that id. | List payouts with `GET /finance/payouts`. |\n| `400` | — | Payout cannot be processed | The payout is not in a state that allows processing — typically it has not been approved. | Approve it first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payouts/{payoutId}/complete`\n- `POST /finance/payouts/{payoutId}/fail`"}},"/finance/payouts/{payoutId}/complete":{"post":{"operationId":"PayoutController_completePayout","summary":"Complete a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"201":{"description":"The completed payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Payout status \"<status>\" cannot be completed — The payout is not in a state that can be completed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Payout status \"<status>\" cannot be completed","path":"/finance/payouts/{payoutId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/finance/payouts/{payoutId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Marks a payout as successfully paid and debits the wallet. Record the `externalReference` from whatever moved the money — a bank transfer reference, a provider payout id — so the platform record can be reconciled against the bank.\n\n#### Signature\n\n```http\nPOST /finance/payouts/{payoutId}/complete (payoutId: string, body) -> The completed payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Always record `externalReference` — without it, a platform payout cannot be matched to a bank line.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout not found | No payout in the org has that id. | List payouts with `GET /finance/payouts`. |\n| `400` | — | Payout status \"<status>\" cannot be completed | The payout is not in a state that can be completed. | A payout must be approved and processing before it can complete. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payouts/{payoutId}/fail`","requestBody":{"description":"The external reference for the transfer.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"externalReference":{"type":"string","description":"Reference from the system that moved the money.","example":"BACS-99182"}}},"example":{"externalReference":"BACS-99182"}}}}}},"/finance/payouts/{payoutId}/fail":{"post":{"operationId":"PayoutController_failPayout","summary":"Fail a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"201":{"description":"The failed payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/finance/payouts/{payoutId}/fail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Records that a payout did not go through, with the reason. The wallet balance is not debited, so the funds remain available to try again.\n\n#### Signature\n\n```http\nPOST /finance/payouts/{payoutId}/fail (payoutId: string, body) -> The failed payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Fix the payout method before requesting again, or the next attempt fails the same way.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout not found | No payout in the org has that id. | List payouts with `GET /finance/payouts`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payouts/{payoutId}/complete`","requestBody":{"description":"Why it failed.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","example":"Bank rejected — account closed"},"code":{"type":"string","description":"Provider failure code.","example":"account_closed"}}},"example":{"reason":"Bank rejected — account closed","code":"account_closed"}}}}}},"/finance/payouts/{payoutId}/cancel":{"post":{"operationId":"PayoutController_cancelPayout","summary":"Cancel a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"201":{"description":"The cancelled payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/finance/payouts/{payoutId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Cancels a payout before it is paid, releasing the amount back to the wallet. Use this to withdraw a request; use `fail` when an attempt was made and rejected.\n\n#### Signature\n\n```http\nPOST /finance/payouts/{payoutId}/cancel (payoutId: string, body) -> The cancelled payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout not found | No payout in the org has that id. | List payouts with `GET /finance/payouts`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payouts/{payoutId}/fail`","requestBody":{"description":"Why it is being cancelled.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Requested in error"}}},"example":{"reason":"Requested in error"}}}}}},"/finance/batch/payouts":{"post":{"operationId":"PayoutController_processBatchPayouts","summary":"Process payouts for several wallets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Per-wallet results, including those skipped","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Creates payouts across many wallets in one call — the monthly settlement run.\n\nUse `minAmount` to skip wallets whose balance is not worth a transfer. Wallets that fail their own rules (below minimum, no payout method, frozen) are skipped rather than failing the batch, so **inspect the response** to see which ones actually produced a payout.\n\n#### Signature\n\n```http\nPOST /finance/batch/payouts (body) -> Per-wallet results, including those skipped\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- A `200` means the batch ran, not that every wallet was paid. Check each result.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/wallets/{walletId}/request-payout`","requestBody":{"description":"Which wallets to pay out, and a floor.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["walletIds"],"properties":{"walletIds":{"type":"array","items":{"type":"string"},"description":"Wallets to process.","example":["WAL-4821","WAL-4822"]},"minAmount":{"type":"number","description":"Skip wallets whose available balance is below this.","example":25}}},"example":{"walletIds":["WAL-4821","WAL-4822"],"minAmount":25}}}}}},"/finance/stats/wallets":{"get":{"operationId":"PayoutController_getWalletStats","summary":"Get wallet statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Aggregate wallet statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Wallets"],"description":"Aggregate wallet figures for the org — how many wallets exist and how much is held across them. The outstanding-liability view.\n\n#### Signature\n\n```http\nGET /finance/stats/wallets () -> Aggregate wallet statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/stats/payouts`"}},"/finance/stats/payouts":{"get":{"operationId":"PayoutController_getPayoutStats","summary":"Get payout statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Aggregate payout statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Aggregate payout figures for the org — volume and value by status, including how much is pending approval.\n\n#### Signature\n\n```http\nGET /finance/stats/payouts () -> Aggregate payout statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/stats/wallets`"}},"/finance/payout-batches/eligible":{"get":{"operationId":"PayoutController_listEligibleForBatch","summary":"List bank payouts waiting for a batch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Eligible payouts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"payoutNumber":{"type":"string"},"recipientName":{"type":"string"},"amount":{"type":"number","description":"Net amount."},"status":{"type":"string"},"accountLast4":{"type":"string"}}}},"example":[{"id":"PO-4821","payoutNumber":"PO-2026-000412","recipientName":"Ana Ruiz","amount":1250,"status":"approved","accountLast4":"6789"}]}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · ACH batches"],"description":"Bank (`method: bank`) payouts that are approved or processing and not yet in an ACH batch — what `POST /finance/payout-batches` would pick up.\n\n#### Signature\n\n```http\nGET /finance/payout-batches/eligible () -> Eligible payouts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payout-batches`"}},"/finance/payout-batches":{"post":{"operationId":"PayoutController_buildPayoutBatch","summary":"Build an ACH batch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The batch and its file","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"string"},"batchNumber":{"type":"number"},"entryCount":{"type":"number"},"totalAmount":{"type":"number"},"createdAt":{"type":"string"},"payoutIds":{"type":"array","items":{"type":"string"}},"content":{"type":"string","description":"The NACHA file text. Contains full account numbers."}}}}}},"400":{"description":"ACH is not configured for this organization — missing <fields>. Set it up before building a batch. — The org’s ACH originator (the `ach` integration or `payout_ach` setting) lacks originatorName, originatorId or odfiRoutingNumber.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"ACH is not configured for this organization — missing <fields>. Set it up before building a batch.","path":"/finance/payout-batches","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · ACH batches"],"description":"Builds a NACHA file from the given bank payouts (or every eligible one), stamps each payout with the batch id and its trace number, and returns the file. Every destination is checked first, so one bad routing number fails the whole build before anything is stamped. Batch numbers are sequential per organization. Building does not send anything — the file does nothing until it is uploaded to the bank and marked sent. When the ACH config has a `notifyEmail`, that address is told the file is waiting.\n\n#### Signature\n\n```http\nPOST /finance/payout-batches (body) -> The batch and its file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- The returned file holds full account numbers — treat it as sensitive.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | ACH is not configured for this organization — missing <fields>. Set it up before building a batch. | The org’s ACH originator (the `ach` integration or `payout_ach` setting) lacks originatorName, originatorId or odfiRoutingNumber. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payout-batches/{batchId}/sent`\n- `GET /finance/payout-batches/{batchId}/file`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"payoutIds":{"type":"array","items":{"type":"string"},"description":"Default: every eligible payout."},"effectiveDate":{"type":"string","description":"Effective entry date (ISO)."}}},"example":{"payoutIds":["PO-4821","PO-4822"],"effectiveDate":"2026-10-01"}}}}},"get":{"operationId":"PayoutController_listPayoutBatches","summary":"List ACH batches","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Batches","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"batchId":{"type":"string"},"batchNumber":{"type":"number"},"createdAt":{"type":"string"},"entryCount":{"type":"number"},"totalAmount":{"type":"number"},"statuses":{"type":"object","additionalProperties":{"type":"number"},"description":"Payouts in the batch by status."},"status":{"type":"string"},"currency":{"type":"string"},"returnedCount":{"type":"number"},"returnedAmount":{"type":"number"},"sentAt":{"type":"string"},"settledAt":{"type":"string"},"bankReference":{"type":"string"},"settled":{"type":"boolean"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · ACH batches"],"description":"Every batch, newest first, with its entry count, total, status (built, sent, settled, partly_returned), return counts and dates. Metadata only — the file content is served by the download endpoint.\n\n#### Signature\n\n```http\nGET /finance/payout-batches () -> Batches\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/finance/payout-batches/{batchId}/file":{"get":{"operationId":"PayoutController_getPayoutBatchFile","summary":"Download an ACH batch file","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"batchId","required":true,"in":"path","schema":{"type":"string"},"description":"Batch id."}],"responses":{"200":{"description":"The file","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"string"},"content":{"type":"string"},"entryCount":{"type":"number"},"stored":{"type":"boolean"},"checksum":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Batch not found — No stored file and no payouts carry that batch id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Batch not found","path":"/finance/payout-batches/{batchId}/file","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · ACH batches"],"description":"The NACHA file built for the batch, from the stored bytes (`stored: true`, with a checksum). Batches built before files were stored are regenerated from the payouts and the current ACH config — faithful only while neither has changed.\n\n#### Signature\n\n```http\nGET /finance/payout-batches/{batchId}/file (batchId: string) -> The file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- Contains full account numbers.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Batch not found | No stored file and no payouts carry that batch id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/finance/payout-batches/{batchId}/sent":{"post":{"operationId":"PayoutController_markPayoutBatchSent","summary":"Mark an ACH batch sent to the bank","description":"Records that the file was delivered to the bank, when, by whom, with an optional note and bank reference. Building and delivering are separate acts; only the operator knows when the second happened. Payouts stay in flight either way.\n\n#### Signature\n\n```http\nPOST /finance/payout-batches/{batchId}/sent (batchId: string, body) -> Done\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Batch not found | No batch record has that id (batches built before batch records existed cannot be marked). | — |\n| `400` | — | This batch is already settled. | The batch is settled or partly_returned. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payout-batches/{batchId}/settle`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"batchId","required":true,"in":"path","schema":{"type":"string"},"description":"Batch id."}],"responses":{"201":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"string"},"status":{"type":"string","enum":["sent"]}}}}}},"400":{"description":"This batch is already settled. — The batch is settled or partly_returned.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This batch is already settled.","path":"/finance/payout-batches/{batchId}/sent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Batch not found — No batch record has that id (batches built before batch records existed cannot be marked).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Batch not found","path":"/finance/payout-batches/{batchId}/sent","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · ACH batches"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"},"reference":{"type":"string","description":"Bank upload reference."}}},"example":{"reference":"UPL-88421","note":"Uploaded to Chase ACH portal"}}}}}},"/finance/payout-batches/{batchId}/settle":{"post":{"operationId":"PayoutController_settlePayoutBatch","summary":"Mark an ACH batch settled","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"batchId","required":true,"in":"path","schema":{"type":"string"},"description":"Batch id."}],"responses":{"201":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"string"},"settled":{"type":"number"},"skipped":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"}}}},"payoutIds":{"type":"array","items":{"type":"string"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Batch not found — No payouts carry that batch id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Batch not found","path":"/finance/payout-batches/{batchId}/settle","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · ACH batches"],"description":"The bank confirmed the file: completes every payout in the batch still approved or processing — which debits each wallet and writes its ledger row — and marks the batch settled. Payouts already completed, failed or cancelled are skipped and reported.\n\n#### Signature\n\n```http\nPOST /finance/payout-batches/{batchId}/settle (batchId: string, body) -> Result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Batch not found | No payouts carry that batch id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payout-batches/{batchId}/items/{payoutId}/return`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string","description":"Bank reference. Default: each payout’s trace number."}}}}}}}},"/finance/payout-batches/{batchId}/items/{payoutId}/return":{"post":{"operationId":"PayoutController_returnPayoutBatchItem","summary":"Record an ACH return","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"batchId","required":true,"in":"path","schema":{"type":"string"},"description":"Batch id."},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"201":{"description":"The failed payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"400":{"description":"Payout cannot be failed — The payout is already completed, failed or cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Payout cannot be failed","path":"/finance/payout-batches/{batchId}/items/{payoutId}/return","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout is not part of this batch — The payout does not exist or carries a different batch id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout is not part of this batch","path":"/finance/payout-batches/{batchId}/items/{payoutId}/return","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · ACH batches"],"description":"One entry came back from the bank: fails that payout (reason `ach_return`), which releases its reserve, and marks the batch `partly_returned` with the returned count and amount. The rest of the batch is untouched.\n\n#### Signature\n\n```http\nPOST /finance/payout-batches/{batchId}/items/{payoutId}/return (batchId: string, payoutId: string, body) -> The failed payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout is not part of this batch | The payout does not exist or carries a different batch id. | — |\n| `400` | — | Payout cannot be failed | The payout is already completed, failed or cancelled. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Default: \"Returned by the bank\"."}}},"example":{"reason":"R03 — no account / unable to locate account"}}}}}},"/finance/payouts/run-schedule":{"post":{"operationId":"PayoutController_runScheduledPayouts","summary":"Run the payout schedule","description":"Pays out each eligible wallet’s whole available balance, as the schedule would. With the org’s schedule set to `manual` nothing runs (`ran: false`) unless `force` is true. Wallets set to manual, without a destination, with nothing available, below their own minimum, or already mid-payout are skipped and reported with the reason.\n\n#### Signature\n\n```http\nPOST /finance/payouts/run-schedule (body) -> What ran\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What ran","content":{"application/json":{"schema":{"type":"object","properties":{"ran":{"type":"boolean"},"schedule":{"type":"string"},"created":{"type":"array","items":{"type":"object","properties":{"walletId":{"type":"string"},"payoutId":{"type":"string"},"amount":{"type":"number"},"status":{"type":"string"}}}},"skipped":{"type":"array","items":{"type":"object","properties":{"walletId":{"type":"string"},"reason":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"force":{"type":"boolean","description":"Run even when the schedule is manual."}}},"example":{"force":true}}}}}},"/finance/payouts/sweep":{"post":{"operationId":"PayoutController_sweepPayouts","summary":"Refresh every in-flight payout","description":"Backstop for a missed webhook: asks each rail where the org’s processing payouts stand, completing or failing them accordingly. Bank payouts are skipped — they settle with their ACH batch. One unreachable rail does not stop the sweep. Safe to call on a schedule.\n\n#### Signature\n\n```http\nPOST /finance/payouts/sweep () -> Counts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payouts/{payoutId}/refresh-status`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Counts","content":{"application/json":{"schema":{"type":"object","properties":{"checked":{"type":"number"},"skipped":{"type":"number"},"settled":{"type":"number"},"failed":{"type":"number"}}},"example":{"checked":4,"skipped":2,"settled":3,"failed":0}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"]}},"/finance/payouts/{payoutId}/refresh-status":{"post":{"operationId":"PayoutController_refreshPayoutStatus","summary":"Refresh one payout from its rail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"201":{"description":"The payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","description":"Human-readable number.","example":"PAY-2026-00412"},"walletId":{"type":"string","example":"WAL-4821"},"recipientType":{"type":"string","example":"host"},"recipientId":{"type":"string","example":"cus_4821"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","description":"Payout destination used.","example":"bank_transfer"},"externalReference":{"type":"string","description":"Reference from the system that actually moved the money.","example":"BACS-99182"},"failureReason":{"type":"string"},"notes":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout in the org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/finance/payouts/{payoutId}/refresh-status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · Payouts"],"description":"Asks the payout’s rail where it stands. Only a `processing` payout is checked; completed on the rail completes it here, failed fails it, anything else leaves it as is. A payout not processing, or on a rail with no status check, comes back unchanged.\n\n#### Signature\n\n```http\nPOST /finance/payouts/{payoutId}/refresh-status (payoutId: string) -> The payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Payout not found | No payout in the org has that id. | List payouts with `GET /finance/payouts`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/client/finance/wallet":{"get":{"operationId":"FinanceClientController_getMyWallet","summary":"Get my wallet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The caller's wallet, transactions and summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"wallet":{"type":"object","description":"The wallet.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"balance":{"type":"number","example":1250},"availableBalance":{"type":"number","description":"Balance minus holds — what can be paid out.","example":1000},"currency":{"type":"string","example":"USD"},"status":{"type":"string","example":"active"}}}}},"transactions":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Movement history."},"summary":{"type":"object","additionalProperties":true,"description":"Totals earned, paid out and pending."}}}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/wallet","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — The caller has no wallet, **or** the wallet found does not belong to them.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/client/finance/wallet","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"The signed-in recipient's wallet, with its transaction history and a summary — the read behind an \"earnings\" page.\n\n`balance` includes any funds under hold; `availableBalance` is what can actually be withdrawn. Show the second figure when telling someone what they can request.\n\n#### Signature\n\n```http\nGET /client/finance/wallet () -> The caller's wallet, transactions and summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A recipient who has never been credited has no wallet yet and gets a `404` — treat that as a zero balance in a UI, not an error.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `404` | — | Wallet not found | The caller has no wallet, **or** the wallet found does not belong to them. | A wallet is created when the first earning is credited. The same `404` covers both cases deliberately, so it cannot be used to detect another party's wallet. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/finance/payouts`"}},"/client/finance/payouts":{"get":{"operationId":"FinanceClientController_getMyPayouts","summary":"Get my payouts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"]},"example":"completed"},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"A page of the caller's payouts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","example":"PAY-2026-00412"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","example":"bank_transfer"},"externalReference":{"type":"string","description":"Set once the transfer is complete.","example":"BACS-99182"},"failureReason":{"type":"string"}}}}}},"total":{"type":"integer","example":12}}}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payouts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"The caller's own payout history, with optional status filtering and paging.\n\n#### Signature\n\n```http\nGET /client/finance/payouts (status?: string, page?: integer, pageSize?: integer) -> A page of the caller's payouts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/finance/payouts/{payoutId}`"}},"/client/finance/payouts/{payoutId}":{"get":{"operationId":"FinanceClientController_getMyPayout","summary":"Get one of my payouts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payoutId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout id.","example":"PO-4821"}],"responses":{"200":{"description":"The payout","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","example":"PAY-2026-00412"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","example":"bank_transfer"},"externalReference":{"type":"string","description":"Set once the transfer is complete.","example":"BACS-99182"},"failureReason":{"type":"string"}}}}}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payouts/{payoutId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Payout not found — No payout has that id, **or** it belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Payout not found","path":"/client/finance/payouts/{payoutId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"Fetches one of the caller's payouts, including its status and — once paid — the external reference for the transfer.\n\n#### Signature\n\n```http\nGET /client/finance/payouts/{payoutId} (payoutId: string) -> The payout\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Another recipient's payout returns `404`, not `403` — existence is never confirmed to a caller who does not own it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `404` | — | Payout not found | No payout has that id, **or** it belongs to someone else. | Both cases return the same `404` by design. List your own payouts with `GET /client/finance/payouts`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/finance/payouts`"}},"/client/finance/payouts/request":{"post":{"operationId":"FinanceClientController_requestPayout","summary":"Request a payout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created payout, pending approval","content":{"application/json":{"schema":{"type":"object","description":"A payout (`finance_payout`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"payoutId":{"type":"string","example":"PO-4821"},"payoutNumber":{"type":"string","example":"PAY-2026-00412"},"amount":{"type":"number","example":500},"currency":{"type":"string","example":"USD"},"status":{"type":"string","enum":["pending","approved","processing","completed","failed","cancelled"],"example":"pending"},"method":{"type":"string","example":"bank_transfer"},"externalReference":{"type":"string","description":"Set once the transfer is complete.","example":"BACS-99182"},"failureReason":{"type":"string"}}}}}}}},"400":{"description":"Insufficient available balance (<n>, need <n>) — The amount exceeds `availableBalance`. Held funds do not count towards it.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Insufficient available balance (<n>, need <n>)","path":"/client/finance/payouts/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payouts/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — The caller has no wallet, **or** the wallet found does not belong to them.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/client/finance/payouts/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"Requests a withdrawal from the caller's own wallet. The payout starts `pending` and waits for approval — requesting does not move money.\n\nName a `methodId` to be paid to a specific destination, or omit it to use the default method.\n\nThe same rules as the operator endpoint apply: the amount must sit within the available balance and inside the wallet's minimum, maximum and daily limits, and the chosen method must exist and be enabled.\n\n#### Signature\n\n```http\nPOST /client/finance/payouts/request (body) -> The created payout, pending approval\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Requesting is not the same as being paid. The payout must be approved, processed and completed before money arrives.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `404` | — | Wallet not found | The caller has no wallet, **or** the wallet found does not belong to them. | A wallet is created when the first earning is credited. The same `404` covers both cases deliberately, so it cannot be used to detect another party's wallet. |\n| `400` | — | Insufficient available balance (<n>, need <n>) | The amount exceeds `availableBalance`. Held funds do not count towards it. | Show `availableBalance`, not `balance`, as the withdrawable figure. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/finance/payout-methods`\n- `GET /client/finance/payouts`","requestBody":{"description":"How much to withdraw, and where to.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Amount to withdraw, within `availableBalance`.","example":500},"methodId":{"type":"string","description":"Payout method to use. Omit for the default.","example":"PM-4821"},"notes":{"type":"string","example":"August earnings"}}},"examples":{"default":{"summary":"Withdraw to the default method","value":{"amount":500}},"specific":{"summary":"Withdraw to a named method","value":{"amount":500,"methodId":"PM-4821","notes":"August earnings"}}}}}}}},"/client/finance/payout-settings":{"put":{"operationId":"FinanceClientController_updatePayoutSettings","summary":"Update my payout settings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The updated settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payout-settings","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Wallet not found — The caller has no wallet, **or** the wallet found does not belong to them.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Wallet not found","path":"/client/finance/payout-settings","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"Updates the caller's own payout preferences — such as an automatic payout schedule or a preferred destination.\n\nPlatform-imposed limits (minimum, maximum, daily cap) are set by the operator through `PUT /finance/wallets/{walletId}/payout-settings` and are not raised from here.\n\n#### Signature\n\n```http\nPUT /client/finance/payout-settings (body) -> The updated settings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n| `404` | — | Wallet not found | The caller has no wallet, **or** the wallet found does not belong to them. | A wallet is created when the first earning is credited. The same `404` covers both cases deliberately, so it cannot be used to detect another party's wallet. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /finance/wallets/{walletId}/payout-settings`","requestBody":{"description":"The settings to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"autoPayoutEnabled":{"type":"boolean","description":"Pay out automatically when the balance allows.","example":true},"autoPayoutThreshold":{"type":"number","description":"Balance at which an automatic payout triggers.","example":100},"defaultMethodId":{"type":"string","example":"PM-4821"}}},"example":{"autoPayoutEnabled":true,"autoPayoutThreshold":100,"defaultMethodId":"PM-4821"}}}}}},"/client/finance/payout-methods":{"get":{"operationId":"FinanceClientController_getPayoutMethods","summary":"Get my payout methods","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The caller's payout methods","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"methodId":{"type":"string","example":"PM-4821"},"type":{"type":"string","enum":["bank","paypal","venmo","cashapp","debit_card","crypto"],"example":"bank"},"label":{"type":"string","description":"Name shown to the recipient.","example":"Chase checking"},"isDefault":{"type":"boolean","description":"Used when a payout request names no method.","example":true},"status":{"type":"string","enum":["pending","verified","disabled"],"description":"A `disabled` method is rejected at payout time.","example":"verified"}}}}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payout-methods","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"The caller's configured payout destinations — bank account, PayPal, Venmo, Cash App, debit card or crypto.\n\nSensitive details are stored but are not returned in full; expect masked values such as a last-four rather than a complete account number.\n\n#### Signature\n\n```http\nGET /client/finance/payout-methods () -> The caller's payout methods\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A method with `status: \"pending\"` has not been verified yet and may be refused at payout time.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/finance/payout-methods`"},"post":{"operationId":"FinanceClientController_addPayoutMethod","summary":"Add a payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created payout method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"methodId":{"type":"string","example":"PM-4821"},"type":{"type":"string","enum":["bank","paypal","venmo","cashapp","debit_card","crypto"],"example":"bank"},"label":{"type":"string","description":"Name shown to the recipient.","example":"Chase checking"},"isDefault":{"type":"boolean","description":"Used when a payout request names no method.","example":true},"status":{"type":"string","enum":["pending","verified","disabled"],"description":"A `disabled` method is rejected at payout time.","example":"verified"}}}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payout-methods","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"Adds a destination the caller can be paid to. **`type` decides which detail block is read** — send the block matching the type and leave the others out:\n\n| `type` | Block | Key fields |\n| --- | --- | --- |\n| `bank` | `bank` | `routingNumber`, `accountNumber`, `accountHolderName` |\n| `debit_card` | `debitCard` | `token`, `last4`, expiry |\n| `paypal` | `paypal` | `email` |\n| `venmo` | `venmo` | `handle` or `phoneNumber` |\n| `cashapp` | `cashapp` | `cashtag` or `phoneNumber` |\n| `crypto` | `crypto` | `currency`, `address`, `network` |\n\nSet `isDefault` to make it the destination used when a payout request names no method. A new method typically starts `pending` until verified.\n\n#### Signature\n\n```http\nPOST /client/finance/payout-methods (body) -> The created payout method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Blocks that do not match `type` are ignored rather than rejected — sending the wrong one gives a method with no usable details.\n- For crypto, the `network` must match the address. A mismatch sends funds somewhere unrecoverable.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/finance/payout-methods/{methodId}/default`","requestBody":{"description":"The destination to add. Send only the block matching `type`.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["bank","paypal","venmo","cashapp","debit_card","crypto"],"description":"Decides which detail block is read.","example":"bank"},"label":{"type":"string","description":"Name shown to the recipient.","example":"Chase checking"},"isDefault":{"type":"boolean","description":"Make this the default destination.","example":true},"bank":{"type":"object","description":"Read when `type` is `bank`.","properties":{"bankName":{"type":"string","example":"Chase"},"accountType":{"type":"string","enum":["checking","savings"],"example":"checking"},"routingNumber":{"type":"string","example":"021000021"},"accountNumber":{"type":"string","description":"Stored securely and returned masked.","example":"000123456789"},"accountHolderName":{"type":"string","example":"Ada Lovelace"},"accountHolderType":{"type":"string","enum":["individual","business"],"example":"individual"}}},"debitCard":{"type":"object","description":"Read when `type` is `debit_card`. Send a `token` from your payment provider rather than a raw card number.","properties":{"cardBrand":{"type":"string","example":"visa"},"last4":{"type":"string","example":"4242"},"expirationMonth":{"type":"number","example":12},"expirationYear":{"type":"number","example":2029},"cardholderName":{"type":"string","example":"Ada Lovelace"},"token":{"type":"string","example":"tok_1PabcXYZ"}}},"paypal":{"type":"object","description":"Read when `type` is `paypal`.","properties":{"email":{"type":"string","example":"ada@example.com"}}},"venmo":{"type":"object","description":"Read when `type` is `venmo`.","properties":{"handle":{"type":"string","example":"@ada-lovelace"},"phoneNumber":{"type":"string","example":"+15551234567"}}},"cashapp":{"type":"object","description":"Read when `type` is `cashapp`.","properties":{"cashtag":{"type":"string","example":"$adalovelace"},"phoneNumber":{"type":"string","example":"+15551234567"}}},"crypto":{"type":"object","description":"Read when `type` is `crypto`.","properties":{"currency":{"type":"string","example":"USDC"},"address":{"type":"string","example":"0x1234…"},"network":{"type":"string","description":"Sending to the wrong network loses the funds irrecoverably.","example":"ethereum"}}}}},"examples":{"bank":{"summary":"US bank account","value":{"type":"bank","label":"Chase checking","isDefault":true,"bank":{"bankName":"Chase","accountType":"checking","routingNumber":"021000021","accountNumber":"000123456789","accountHolderName":"Ada Lovelace","accountHolderType":"individual"}}},"paypal":{"summary":"PayPal","value":{"type":"paypal","label":"PayPal","paypal":{"email":"ada@example.com"}}},"crypto":{"summary":"Crypto wallet","value":{"type":"crypto","label":"USDC wallet","crypto":{"currency":"USDC","address":"0x1234abcd","network":"ethereum"}}}}}}}}},"/client/finance/payout-methods/{methodId}":{"put":{"operationId":"FinanceClientController_updatePayoutMethod","summary":"Update a payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id.","example":"PM-4821"}],"responses":{"200":{"description":"The updated method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"methodId":{"type":"string","example":"PM-4821"},"type":{"type":"string","enum":["bank","paypal","venmo","cashapp","debit_card","crypto"],"example":"bank"},"label":{"type":"string","description":"Name shown to the recipient.","example":"Chase checking"},"isDefault":{"type":"boolean","description":"Used when a payout request names no method.","example":true},"status":{"type":"string","enum":["pending","verified","disabled"],"description":"A `disabled` method is rejected at payout time.","example":"verified"}}}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payout-methods/{methodId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"Updates a method's label, default flag or status. The banking details themselves cannot be edited here — remove the method and add a new one to change an account number.\n\n#### Signature\n\n```http\nPUT /client/finance/payout-methods/{methodId} (methodId: string, body) -> The updated method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Account details are immutable. Add a replacement method and remove the old one.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/finance/payout-methods/{methodId}`","requestBody":{"description":"What to change.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","example":"Chase checking (personal)"},"isDefault":{"type":"boolean","example":true},"status":{"type":"string","enum":["pending","verified","disabled"],"description":"Set `disabled` to stop using a method without deleting it.","example":"disabled"}}},"examples":{"rename":{"summary":"Rename","value":{"label":"Chase checking (personal)"}},"disable":{"summary":"Stop using it, keep the record","value":{"status":"disabled"}}}}}}},"delete":{"operationId":"FinanceClientController_removePayoutMethod","summary":"Remove a payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id.","example":"PM-4821"}],"responses":{"200":{"description":"Confirmation of the removal","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true}}},"example":{"success":true}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payout-methods/{methodId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"Deletes a payout destination.\n\nRemoving the default leaves the caller without one, and a later payout request that names no method will fail — set a new default first. To stop using a method temporarily, set its status to `disabled` instead.\n\n#### Signature\n\n```http\nDELETE /client/finance/payout-methods/{methodId} (methodId: string) -> Confirmation of the removal\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Pending payouts already routed to this method are not re-routed by removing it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/finance/payout-methods/{methodId}/default`"}},"/client/finance/payout-methods/{methodId}/default":{"put":{"operationId":"FinanceClientController_setDefaultPayoutMethod","summary":"Set the default payout method","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"methodId","required":true,"in":"path","schema":{"type":"string"},"description":"Payout method id.","example":"PM-4821"}],"responses":{"200":{"description":"The updated method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"methodId":{"type":"string","example":"PM-4821"},"type":{"type":"string","enum":["bank","paypal","venmo","cashapp","debit_card","crypto"],"example":"bank"},"label":{"type":"string","description":"Name shown to the recipient.","example":"Chase checking"},"isDefault":{"type":"boolean","description":"Used when a payout request names no method.","example":true},"status":{"type":"string","enum":["pending","verified","disabled"],"description":"A `disabled` method is rejected at payout time.","example":"verified"}}}}}},"401":{"description":"Not authenticated — No signed-in customer or user could be resolved from the request.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/finance/payout-methods/{methodId}/default","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Finance · My account"],"description":"Makes one method the default, used whenever a payout request names none. Setting a new default clears the flag on the previous one.\n\n#### Signature\n\n```http\nPUT /client/finance/payout-methods/{methodId}/default (methodId: string) -> The updated method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | — | Not authenticated | No signed-in customer or user could be resolved from the request. | Sign in. Every endpoint here is scoped to the caller and none work anonymously. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/finance/payout-methods`"}},"/finance/payments/action":{"post":{"operationId":"PaymentController_executeAction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/action","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Execute a payment action","description":"The general form of every payment operation: name the `action` and the `gateway`, and the request is dispatched to that provider.\n\nEach action also has its own alias endpoint — `/charge`, `/refund` and so on — which fills in `action` and is otherwise identical. Use the aliases for readability; use this one when the action is chosen at runtime.\n\n**`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.\n\nA gateway refusal is reported as `success: false` with an `error`, not as an HTTP error status, so check the body — a `200` does not mean the money moved.\n\n#### Signature\n\n```http\nPOST /finance/payments/action (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A `success: false` body is the normal way a decline is reported. Do not treat `200` as proof of payment.\n- Not idempotent — nothing dedupes a repeated charge. Retries must be guarded by the caller.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/charge`\n- `POST /finance/payments/refund`","tags":["Finance · Payments"],"requestBody":{"description":"The action, the gateway, and whatever that action needs.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["action","gateway"],"properties":{"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"description":"What to do.","example":"charge"},"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Required by every action except `charge` and `authorize`.","example":"pi_3PabcXYZ"},"amount":{"type":"number","description":"Amount in **minor units** (cents). **`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.","example":1000},"currency":{"type":"string","description":"ISO 4217 code.","example":"USD"},"customerId":{"type":"string","description":"Gateway customer id.","example":"cus_PabcXYZ"},"paymentMethodId":{"type":"string","description":"Gateway payment-method token.","example":"pm_1PabcXYZ"},"reason":{"type":"string","description":"Reason, recorded with refunds and voids.","example":"Customer returned the item"},"email":{"type":"string","description":"Recipient for `resend_receipt`.","example":"ada@example.com"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"examples":{"charge":{"summary":"Charge $10.00","value":{"action":"charge","gateway":"stripe","amount":1000,"currency":"USD","paymentMethodId":"pm_1PabcXYZ"}},"refund":{"summary":"Refund $10.00","value":{"action":"refund","gateway":"stripe","paymentRef":"pi_3PabcXYZ","amount":1000,"reason":"Customer returned the item"}}}}}}}},"/finance/payments/charge":{"post":{"operationId":"PaymentController_charge","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/charge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Charge a payment method","description":"Takes money immediately: authorizes and captures in one step. This is the default way to collect a payment when there is no reason to hold the funds first.\n\n**`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.\n\nThe `paymentRef` in the response is the handle for every later action — refund, verify, receipt — so store it against your order.\n\n#### Signature\n\n```http\nPOST /finance/payments/charge (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent. Guard against double submission — a repeated request charges twice.\n- Use `authorize` instead when you need to confirm stock or availability before taking the money.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/authorize`\n- `POST /finance/payments/refund`","tags":["Finance · Payments"],"requestBody":{"description":"What to charge, and against what.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","amount"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"amount":{"type":"number","description":"Amount in **minor units** (cents). **`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.","example":1000},"currency":{"type":"string","description":"ISO 4217 code.","example":"USD"},"customerId":{"type":"string","example":"cus_PabcXYZ"},"paymentMethodId":{"type":"string","description":"Payment-method token to charge.","example":"pm_1PabcXYZ"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"examples":{"card":{"summary":"Charge $10.00 to a saved card","value":{"gateway":"stripe","amount":1000,"currency":"USD","customerId":"cus_PabcXYZ","paymentMethodId":"pm_1PabcXYZ"}},"withMetadata":{"summary":"Charge with order metadata","value":{"gateway":"stripe","amount":12999,"currency":"USD","paymentMethodId":"pm_1PabcXYZ","metadata":{"orderNumber":"A7K2M9QX4"}}}}}}}}},"/finance/payments/authorize":{"post":{"operationId":"PaymentController_authorize","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/authorize","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Authorize a payment","description":"Reserves funds on the customer's card without taking them. The money is held but not moved, and the authorization must later be captured or voided.\n\nUse this when the charge should be contingent — confirming stock, weighing a shipment, or adding a tip after the card is presented.\n\n**Authorizations expire**, typically within a week, and the window is the gateway's. An expired authorization cannot be captured.\n\n**`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.\n\n#### Signature\n\n```http\nPOST /finance/payments/authorize (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Every authorization must be captured or voided. One left alone holds the customer's funds until the gateway expires it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/capture`\n- `POST /finance/payments/void`","tags":["Finance · Payments"],"requestBody":{"description":"What to authorize.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","amount"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"amount":{"type":"number","description":"Amount in **minor units** (cents). **`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.","example":1000},"currency":{"type":"string","description":"ISO 4217 code.","example":"USD"},"customerId":{"type":"string","example":"cus_PabcXYZ"},"paymentMethodId":{"type":"string","example":"pm_1PabcXYZ"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"example":{"gateway":"stripe","amount":1000,"currency":"USD","paymentMethodId":"pm_1PabcXYZ"}}}}}},"/finance/payments/capture":{"post":{"operationId":"PaymentController_capture","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/capture","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Capture an authorized payment","description":"Takes the funds an authorization was holding. Pass `amount` to capture less than was authorized — most gateways allow a partial capture but not one above the authorized amount.\n\nOmitting `amount` captures the full authorization.\n\n**`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.\n\n#### Signature\n\n```http\nPOST /finance/payments/capture (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- An expired or already-captured authorization is refused by the gateway and comes back as `success: false`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/authorize`","tags":["Finance · Payments"],"requestBody":{"description":"Which authorization to capture, and how much.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference from the original charge or authorization.","example":"pi_3PabcXYZ"},"amount":{"type":"number","description":"Partial capture amount in minor units. Omit for the full authorization. **`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.","example":1000},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"examples":{"full":{"summary":"Capture the full authorization","value":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ"}},"partial":{"summary":"Capture $8.00 of a larger hold","value":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ","amount":800}}}}}}}},"/finance/payments/refund":{"post":{"operationId":"PaymentController_refund","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Refund a payment","description":"Returns money for a payment that was captured. Pass `amount` for a partial refund; omit it to refund in full.\n\nRefunds can usually be issued more than once against a payment until the original total is reached, so this is **not idempotent** — a retry refunds again.\n\n**`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.\n\n#### Signature\n\n```http\nPOST /finance/payments/refund (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent. Verify with `verify` before retrying a refund whose response you lost.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/void`\n- `POST /finance/payments/verify`","tags":["Finance · Payments"],"requestBody":{"description":"Which payment to refund, and how much.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference from the original charge or authorization.","example":"pi_3PabcXYZ"},"amount":{"type":"number","description":"Partial refund in minor units. Omit for a full refund. **`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.","example":1000},"reason":{"type":"string","example":"Customer returned the item"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"examples":{"full":{"summary":"Full refund","value":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ","reason":"Customer returned the item"}},"partial":{"summary":"Refund $5.00","value":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ","amount":500,"reason":"One item returned"}}}}}}}},"/finance/payments/void":{"post":{"operationId":"PaymentController_void","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/void","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Void an authorization","description":"Releases an authorization without taking the money, freeing the hold on the customer's card.\n\nVoid is for money that was never captured. Once funds have been taken, use `refund` instead — the two are not interchangeable, and gateways treat them differently.\n\n#### Signature\n\n```http\nPOST /finance/payments/void (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A captured payment cannot be voided. Refund it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/refund`\n- `POST /finance/payments/authorize`","tags":["Finance · Payments"],"requestBody":{"description":"Which authorization to release.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference from the original charge or authorization.","example":"pi_3PabcXYZ"},"reason":{"type":"string","example":"Order cancelled before dispatch"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"example":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ","reason":"Order cancelled before dispatch"}}}}}},"/finance/payments/verify":{"post":{"operationId":"PaymentController_verify","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/verify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Verify a payment","description":"Asks the gateway for the current state of a payment, rather than trusting what a client reported or what was last written locally.\n\nThis is the call to make when a response was lost, before retrying anything — it tells you whether the money actually moved.\n\n#### Signature\n\n```http\nPOST /finance/payments/verify (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only — safe to call as often as needed.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/retry`","tags":["Finance · Payments"],"requestBody":{"description":"Which payment to check.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference from the original charge or authorization.","example":"pi_3PabcXYZ"}}},"example":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ"}}}}}},"/finance/payments/cancel":{"post":{"operationId":"PaymentController_cancel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Cancel a payment","description":"Cancels a payment that is still in progress at the gateway — one awaiting confirmation or a customer action, which is neither a live authorization to void nor a captured payment to refund.\n\n#### Signature\n\n```http\nPOST /finance/payments/cancel (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Only applies to payments the gateway still considers pending. Use `void` for an authorization and `refund` for a captured payment.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/void`","tags":["Finance · Payments"],"requestBody":{"description":"Which payment to cancel.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference from the original charge or authorization.","example":"pi_3PabcXYZ"},"reason":{"type":"string","example":"Customer abandoned checkout"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"example":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ","reason":"Customer abandoned checkout"}}}}}},"/finance/payments/resend-receipt":{"post":{"operationId":"PaymentController_resendReceipt","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/resend-receipt","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Resend a payment receipt","description":"Asks the gateway to resend its receipt for a payment. Pass `email` to send it somewhere other than the address on the original payment.\n\n#### Signature\n\n```http\nPOST /finance/payments/resend-receipt (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends the *gateway's* receipt, not the platform's order confirmation. For that, use `GET /storefront/order/send-welcome/{orderNumber}`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /storefront/order/send-welcome/{orderNumber}`","tags":["Finance · Payments"],"requestBody":{"description":"Which payment, and where to send the receipt.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference from the original charge or authorization.","example":"pi_3PabcXYZ"},"email":{"type":"string","description":"Override the recipient. Defaults to the address on the payment.","example":"ada@example.com"}}},"example":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ","email":"ada@example.com"}}}}}},"/finance/payments/retry":{"post":{"operationId":"PaymentController_retry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/retry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Original transaction not found — No transaction matches `paymentRef`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Original transaction not found","path":"/finance/payments/retry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Retry a failed payment","description":"Re-attempts a payment that previously failed, reusing the original transaction's details.\n\nThe original transaction must exist — this replays a known failure rather than creating a fresh payment. Verify the original state first, so a payment that actually succeeded is not charged a second time.\n\n#### Signature\n\n```http\nPOST /finance/payments/retry (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n| `404` | — | Original transaction not found | No transaction matches `paymentRef`. | Retry replays a recorded transaction. Issue a fresh `charge` instead. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/verify`\n- `POST /finance/payments/charge`","tags":["Finance · Payments"],"requestBody":{"description":"Which failed payment to retry.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference of the failed payment.","example":"pi_3PabcXYZ"},"paymentMethodId":{"type":"string","description":"A different payment method to try.","example":"pm_1PdefUVW"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"example":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ","paymentMethodId":"pm_1PdefUVW"}}}}}},"/finance/payments/mark-paid":{"post":{"operationId":"PaymentController_markPaid","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"author","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"The action result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the gateway accepted the action.","example":true},"action":{"type":"string","enum":["charge","authorize","capture","refund","void","verify","cancel","resend_receipt","retry","mark_paid"],"example":"charge"},"paymentRef":{"type":"string","description":"Gateway payment reference. Carry this into every later action on the same payment.","example":"pi_3PabcXYZ"},"status":{"type":"string","description":"Gateway status after the action.","example":"succeeded"},"amount":{"type":"number","description":"Amount acted on, in minor units.","example":1000},"currency":{"type":"string","example":"USD"},"gateway":{"type":"string","example":"stripe"},"error":{"type":"string","description":"Present when `success` is false."},"gatewayResponse":{"type":"object","additionalProperties":true,"description":"The raw provider response, passed through unchanged."},"createdAt":{"type":"string","format":"date-time","example":"2026-08-29T14:03:22.118Z"}}}}}},"400":{"description":"Gateway <gateway> not configured — The named gateway has no integration set up for this org.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Gateway <gateway> not configured","path":"/finance/payments/mark-paid","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction not found — No transaction matches `paymentRef`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction not found","path":"/finance/payments/mark-paid","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Mark a payment as paid","description":"Records a transaction as settled **without moving any money** — for payments taken outside the platform: a bank transfer, a cheque, cash at a counter.\n\nThe gateway is never contacted. This only changes what the ledger says, so use it when the money has genuinely arrived by another route.\n\nThe `author` header is recorded on the transaction, defaulting to `system` when absent — send it so the audit trail names a person.\n\n#### Signature\n\n```http\nPOST /finance/payments/mark-paid (body) -> The action result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No money moves. Only use it when payment has genuinely been received outside the platform.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Gateway <gateway> not configured | The named gateway has no integration set up for this org. | Configure the gateway in the org integration settings. The name is matched against the configured integration. |\n| `404` | — | Transaction not found | No transaction matches `paymentRef`. | The transaction must already exist — this settles a record, it does not create one. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/verify`","tags":["Finance · Payments"],"requestBody":{"description":"Which transaction to mark settled.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Which configured gateway to act through.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference of the transaction to settle.","example":"pi_3PabcXYZ"},"amount":{"type":"number","description":"Amount in **minor units** (cents). **`amount` is in the currency's minor unit — cents, not dollars.** `1000` is $10.00. Sending `10` charges ten cents.","example":1000},"reason":{"type":"string","example":"Paid by bank transfer, ref BACS-99182"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary data passed through to the gateway and stored on the transaction."}}},"example":{"gateway":"manual","paymentRef":"txn_4a91","reason":"Paid by bank transfer, ref BACS-99182"}}}}}},"/finance/payments/gateway-url":{"post":{"operationId":"PaymentController_getGatewayUrl","parameters":[],"responses":{"201":{"description":"The dashboard URL, or an empty string for an unrecognised gateway","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Deep link, or `\"\"`.","example":"https://dashboard.stripe.com/payments/pi_3PabcXYZ"}}},"examples":{"known":{"summary":"A recognised gateway","value":{"url":"https://dashboard.stripe.com/payments/pi_3PabcXYZ"}},"unknown":{"summary":"An unrecognised gateway","value":{"url":""}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get the gateway dashboard URL for a payment","description":"Builds a deep link to a payment in the provider's own dashboard, so an operator can jump from a transaction here to the full record at Stripe, PayPal or Helcim.\n\nThe URL is assembled from a fixed pattern per gateway — nothing is looked up and no provider is contacted, so the link is returned even for a reference that does not exist. **An unrecognised gateway yields an empty string**, not an error.\n\nThis is the only endpoint on the controller that does not read the `orgid` header, because it touches no org data.\n\n#### Signature\n\n```http\nPOST /finance/payments/gateway-url (body) -> The dashboard URL, or an empty string for an unrecognised gateway\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Purely string construction — the link is not validated and may 404 at the provider.\n- Check for an empty `url` before rendering a link.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /finance/payments/gateway-transactions`","tags":["Finance · Payments"],"requestBody":{"description":"The gateway and payment reference.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["gateway","paymentRef"],"properties":{"gateway":{"type":"string","description":"Recognised values are `stripe`, `paypal` and `helcim`, matched case-insensitively. Anything else returns an empty URL.","example":"stripe"},"paymentRef":{"type":"string","description":"Reference from the original charge or authorization.","example":"pi_3PabcXYZ"}}},"example":{"gateway":"stripe","paymentRef":"pi_3PabcXYZ"}}}}}},"/finance/payments/gateway-transactions":{"get":{"operationId":"PaymentController_getGatewayTransactions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"provider","required":false,"in":"query","schema":{"type":"string","default":"StripeProvider"},"description":"Provider to query. Defaults to `StripeProvider`.","example":"StripeProvider"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","default":25},"description":"How many to return.","example":25},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"Lower bound on transaction date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"description":"Upper bound on transaction date.","example":"2026-08-31"},{"name":"startingAfter","required":false,"in":"query","schema":{"type":"string"},"description":"Cursor — the last transaction id from the previous page.","example":"pi_3PabcXYZ"}],"responses":{"200":{"description":"Transactions as the provider reports them","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List transactions from the gateway","description":"Reads transactions **directly from the provider**, not from the platform ledger. Use it to reconcile — to see what the gateway thinks happened, independently of what was recorded here.\n\nPaging is the provider's cursor style: pass the last id you saw as `startingAfter` to continue.\n\n#### Signature\n\n```http\nGET /finance/payments/gateway-transactions (provider?: string, limit?: integer, startDate?: string, endDate?: string, startingAfter?: string) -> Transactions as the provider reports them\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff — a signed-in business user; customer and site-app tokens are refused`.\n\n#### Notes\n\n- The shape is the provider's own and differs between gateways — it is not normalised.\n- This is a live call to the provider and counts against their rate limits.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /finance/payments/verify`","tags":["Finance · Payments"]}},"/events":{"get":{"operationId":"EventsController_getEvents","summary":"List events","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"published"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"conference"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"description":"Lower bound on event date.","example":"2026-09-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"description":"Upper bound on event date.","example":"2026-12-31"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"How many to return.","example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"description":"Offset paging — this module uses `offset`, not `page`.","example":0}],"responses":{"200":{"description":"Events","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Lists events with optional filters and offset paging.\n\nEvents are **created and edited through the repository API**, not here — this controller only reads them and drives their lifecycle.\n\n#### Signature\n\n```http\nGET /events (status?: string, type?: string, fromDate?: string, toDate?: string, limit?: integer, offset?: integer) -> Events\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Use `POST /repository/create` with an event datatype to create one.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{id}`\n- `POST /repository/create`"}},"/events/{id}":{"get":{"operationId":"EventsController_getEvent","summary":"Get an event","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"The event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Event not found — No event has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Event not found","path":"/events/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Fetches one event with its configuration and stats.\n\n#### Signature\n\n```http\nGET /events/{id} (id: string) -> The event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Event not found | No event has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/{id}/publish`"}},"/events/{id}/publish":{"post":{"operationId":"EventsController_publishEvent","summary":"Publish an event","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The published event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Event not found — No event has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Event not found","path":"/events/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Makes an event public and opens ticket sales. The point at which customers can find and buy — check ticket types and pricing before publishing.\n\n#### Signature\n\n```http\nPOST /events/{id}/publish (id: string) -> The published event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Event not found | No event has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/{id}/live`"}},"/events/{id}/live":{"post":{"operationId":"EventsController_setEventLive","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Event not found — No event has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Event not found","path":"/events/{id}/live","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"summary":"Mark an event live","description":"Marks the event as happening now — the state check-in expects. Doors are open.\n\n#### Signature\n\n```http\nPOST /events/{id}/live (id: string) -> The event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Event not found | No event has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/checkin`"}},"/events/{id}/complete":{"post":{"operationId":"EventsController_completeEvent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Event not found — No event has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Event not found","path":"/events/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"summary":"Complete an event","description":"Closes an event that has finished. Final attendance figures settle at this point.\n\n#### Signature\n\n```http\nPOST /events/{id}/complete (id: string) -> The event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Event not found | No event has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/checkin-stats`"}},"/events/{id}/cancel":{"post":{"operationId":"EventsController_cancelEvent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The cancelled event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Event not found — No event has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Event not found","path":"/events/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"summary":"Cancel an event","description":"Cancels an event.\n\nCancelling does **not** automatically refund tickets — issue those separately, per ticket or per booking, so the refund route and amount stay under your control.\n\n#### Signature\n\n```http\nPOST /events/{id}/cancel (id: string, body) -> The cancelled event\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Ticket holders are not refunded by this. Use the ticket or booking refund endpoints.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Event not found | No event has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/refund`\n- `POST /events/bookings/{bookingId}/refund`","requestBody":{"description":"Optional cancellation details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Venue unavailable"}}}}}},"/events/{eventId}/tickets":{"post":{"operationId":"EventsController_issueTicket","summary":"Issue a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The issued ticket","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket type not found — No ticket type has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket type not found","path":"/events/{eventId}/tickets","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Issues a single ticket for an event directly, bypassing the purchase flow. For manual issuance where payment is handled elsewhere.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/tickets (eventId: string, body) -> The issued ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket type not found | No ticket type has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/comp`","requestBody":{"description":"The ticket to issue.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ticketTypeId":"TT-general","holderEmail":"ada@example.com","holderName":"Ada Lovelace"}}}}},"get":{"operationId":"EventsController_getEventTickets","summary":"List event tickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"},{"name":"ticketType","required":false,"in":"query","schema":{"type":"string"},"example":"TT-general"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The tickets issued for an event, filterable by status and type.\n\n#### Signature\n\n```http\nGET /events/{eventId}/tickets (eventId: string, status?: string, ticketType?: string, limit?: integer) -> Tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/tickets/lookup/{eventId}`"}},"/events/tickets/purchase":{"post":{"operationId":"EventsController_purchaseTickets","summary":"Purchase tickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The purchased tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}}},"400":{"description":"Quantity must be at least 1 — A line has a quantity below 1.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Quantity must be at least 1","path":"/events/tickets/purchase","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket type not found — No ticket type has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket type not found","path":"/events/tickets/purchase","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The customer purchase path. Validates each line before issuing anything — the ticket type must be active, within its sale window, and have enough left.\n\nEach check has its own message naming the ticket type, so a failure tells the customer exactly which line is the problem and why.\n\n#### Signature\n\n```http\nPOST /events/tickets/purchase (body) -> The purchased tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Availability is checked at purchase time — a type shown as available when the page loaded can sell out before submission.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket type not found | No ticket type has that id. | Check the id against the corresponding list endpoint. |\n| `400` | INVALID_QUANTITY | Quantity must be at least 1 | A line has a quantity below 1. | Remove the line rather than sending zero. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/ticket-types`","requestBody":{"description":"What to buy, and for whom.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"eventId":{"type":"string","example":"EVT-4821"},"items":{"type":"array","items":{"type":"object","properties":{"ticketTypeId":{"type":"string","example":"TT-general"},"quantity":{"type":"number","description":"Must be at least 1.","example":2}}}},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"payment":{"type":"object","additionalProperties":true}}},"example":{"eventId":"EVT-4821","items":[{"ticketTypeId":"TT-general","quantity":2}],"holderEmail":"ada@example.com","holderName":"Ada Lovelace"}}}}}},"/events/tickets/comp":{"post":{"operationId":"EventsController_issueTicketsWithoutPayment","summary":"Issue complimentary tickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The issued tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket type not found — No ticket type has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket type not found","path":"/events/tickets/comp","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Issues tickets without payment — guest list, press, staff. Sale windows and pricing do not apply, but availability still does.\n\n#### Signature\n\n```http\nPOST /events/tickets/comp (body) -> The issued tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Comps still consume availability — they reduce what is left to sell.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket type not found | No ticket type has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/purchase`","requestBody":{"description":"Who to issue to.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"eventId":"EVT-4821","ticketTypeId":"TT-general","quantity":2,"holderEmail":"press@example.com"}}}}}},"/events/tickets/pre-generate":{"post":{"operationId":"EventsController_preGenerateTickets","summary":"Pre-generate tickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The generated tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Creates a batch of unassigned tickets in advance — printed passes or wristbands produced before anyone has bought one.\n\nEach carries a scannable code but no holder. `activate` assigns one to a person at the point of sale.\n\n#### Signature\n\n```http\nPOST /events/tickets/pre-generate (body) -> The generated tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Generated tickets are inactive until assigned — an unactivated code will not pass check-in.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/activate`","requestBody":{"description":"How many to generate.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["eventId","ticketTypeId","quantity"],"properties":{"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"quantity":{"type":"number","example":500}}},"example":{"eventId":"EVT-4821","ticketTypeId":"TT-general","quantity":500}}}}}},"/events/tickets/{ticketId}/activate":{"post":{"operationId":"EventsController_activateTicket","summary":"Activate a pre-generated ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"201":{"description":"The activated ticket","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/activate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Assigns a pre-generated ticket to a holder and makes it valid — the counter action when someone buys a physical pass on the door.\n\n#### Signature\n\n```http\nPOST /events/tickets/{ticketId}/activate (ticketId: string, body) -> The activated ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/pre-generate`","requestBody":{"description":"Who it belongs to, and how they paid.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"payment":{"type":"object","properties":{"amount":{"type":"number","example":45},"method":{"type":"string","example":"card"},"ref":{"type":"string","example":"ch_3PabcXYZ"}}}}},"example":{"holderEmail":"ada@example.com","holderName":"Ada Lovelace","payment":{"amount":45,"method":"card","ref":"ch_3PabcXYZ"}}}}}}},"/events/tickets/{ticketId}/qr":{"get":{"operationId":"EventsController_getTicketWithQR","summary":"Get a ticket QR code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"200":{"description":"The ticket and its QR payload","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/qr","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Returns the ticket with its scannable QR payload — what a holder shows at the door. The payload is the credential, so treat it as a secret.\n\n#### Signature\n\n```http\nGET /events/tickets/{ticketId}/qr (ticketId: string) -> The ticket and its QR payload\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Anyone with the QR payload can be admitted — do not share ticket images publicly.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/validate`"}},"/events/tickets/validate":{"post":{"operationId":"EventsController_validateTicket","summary":"Validate a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Checks whether a scanned QR payload is a valid ticket, **without** admitting anyone. Use `checkin` to actually record entry.\n\n#### Signature\n\n```http\nPOST /events/tickets/validate (body) -> Validation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Read-only. It does not consume the ticket or record attendance.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/checkin`\n- `POST /events/verify-scan`","requestBody":{"description":"The scanned payload.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["qrPayload"],"properties":{"qrPayload":{"type":"string","description":"The scanned QR contents.","example":"EVT4821-9K2M4H"}}},"example":{"qrPayload":"EVT4821-9K2M4H"}}}}}},"/events/tickets/{ticketId}/transfer":{"post":{"operationId":"EventsController_transferTicket","summary":"Transfer a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"201":{"description":"The transferred ticket","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/transfer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Moves a ticket to a different holder. The code stays valid; the name on it changes.\n\n#### Signature\n\n```http\nPOST /events/tickets/{ticketId}/transfer (ticketId: string, body) -> The transferred ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/cancel`","requestBody":{"description":"The new holder.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"holderEmail":"grace@example.com","holderName":"Grace Hopper"}}}}}},"/events/tickets/{ticketId}/cancel":{"post":{"operationId":"EventsController_cancelTicket","summary":"Cancel a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"201":{"description":"The cancelled ticket","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Cancels a ticket so it will not admit anyone, keeping the record. Set `silent` to skip notifying the holder.\n\nCancelling does not refund — issue that separately.\n\n#### Signature\n\n```http\nPOST /events/tickets/{ticketId}/cancel (ticketId: string, body) -> The cancelled ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- No refund is issued. Use `refund` for that.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/refund`","requestBody":{"description":"Optional cancellation details.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Duplicate purchase"},"silent":{"type":"boolean","default":false,"description":"Skip notifying the holder.","example":false}}},"example":{"reason":"Duplicate purchase"}}}}}},"/events/tickets/{ticketId}":{"delete":{"operationId":"EventsController_deleteTicket","summary":"Delete a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Deletes a ticket outright. Cancelling preserves the record of what was sold — prefer that unless the ticket was issued in error.\n\n#### Signature\n\n```http\nDELETE /events/tickets/{ticketId} (ticketId: string, body) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Unusually, this `DELETE` accepts a body.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/cancel`","requestBody":{"description":"Optional details.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"},"silent":{"type":"boolean"}}},"example":{"reason":"Issued in error"}}}}}},"/events/tickets/{ticketId}/refund":{"post":{"operationId":"EventsController_refundTicket","summary":"Refund a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"201":{"description":"The refund result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Refunds a ticket through the payment route it was bought with, and invalidates it.\n\n#### Signature\n\n```http\nPOST /events/tickets/{ticketId}/refund (ticketId: string, body) -> The refund result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/bookings/{bookingId}/refund`","requestBody":{"description":"Refund details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"amount":45,"reason":"Event cancelled"}}}}}},"/events/bookings/{bookingId}/cancel":{"post":{"operationId":"EventsController_cancelBooking","summary":"Cancel a booking","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking id.","example":"BKG-4821"}],"responses":{"201":{"description":"The cancelled booking","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/events/bookings/{bookingId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Cancels a whole booking — every ticket bought together — rather than one at a time.\n\n#### Signature\n\n```http\nPOST /events/bookings/{bookingId}/cancel (bookingId: string, body) -> The cancelled booking\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Booking not found | No booking has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/bookings/{bookingId}/refund`","requestBody":{"description":"Optional cancellation details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Customer request"}}}}}},"/events/bookings/{bookingId}/refund":{"post":{"operationId":"EventsController_refundPurchase","summary":"Refund a booking","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bookingId","required":true,"in":"path","schema":{"type":"string"},"description":"Booking id.","example":"BKG-4821"}],"responses":{"201":{"description":"The refund result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Booking not found — No booking has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/events/bookings/{bookingId}/refund","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Refunds an entire booking in one operation, rather than refunding each ticket separately.\n\n#### Signature\n\n```http\nPOST /events/bookings/{bookingId}/refund (bookingId: string, body) -> The refund result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Booking not found | No booking has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/refund`","requestBody":{"description":"Refund details.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Event cancelled"}}}}}},"/events/tickets/fulfill":{"post":{"operationId":"EventsController_fulfillTicket","summary":"Fulfil tickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The fulfilment result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Marks tickets as fulfilled — delivered to the holder, physically or by email.\n\n#### Signature\n\n```http\nPOST /events/tickets/fulfill (body) -> The fulfilment result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/tickets`","requestBody":{"description":"Which tickets to fulfil.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ticketIds":["TKT-9912","TKT-9913"]}}}}}},"/events/tickets/lookup/{eventId}":{"get":{"operationId":"EventsController_lookupForFulfillment","summary":"Look up a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"bookingId","required":true,"in":"query","schema":{"type":"string"}},{"name":"email","required":false,"in":"query","schema":{"type":"string"},"description":"Holder email.","example":"ada@example.com"},{"name":"confirmationCode","required":true,"in":"query","schema":{"type":"string"}},{"name":"name","in":"query","required":false,"schema":{"type":"string"},"example":"Ada Lovelace"}],"responses":{"200":{"description":"Matching tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"ticketTypeId":{"type":"string","example":"TT-general"},"holderEmail":{"type":"string","example":"ada@example.com"},"holderName":{"type":"string","example":"Ada Lovelace"},"status":{"type":"string","description":"`pending`, `active`, `used`, `cancelled`, `refunded`.","example":"active"},"code":{"type":"string","description":"Scannable code. What check-in resolves.","example":"EVT4821-9K2M4H"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Finds a ticket for an event by holder details — the door-staff lookup when someone arrives without their code.\n\n#### Signature\n\n```http\nGET /events/tickets/lookup/{eventId} (eventId: string, email?: string, name?: string) -> Matching tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/checkin`"}},"/events/tickets/{ticketId}/perks":{"get":{"operationId":"EventsController_getTicketPerks","summary":"Get ticket perks","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"200":{"description":"The ticket's perks","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/perks","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The perks attached to a ticket — meals, merchandise, lounge access — and which have been claimed.\n\n#### Signature\n\n```http\nGET /events/tickets/{ticketId}/perks (ticketId: string) -> The ticket's perks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/perks/{perkId}/claim`"}},"/events/tickets/{ticketId}/perks/{perkId}/claim":{"post":{"operationId":"EventsController_claimPerk","summary":"Claim a perk","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"},{"name":"perkId","required":true,"in":"path","schema":{"type":"string"},"description":"Perk id.","example":"PRK-lunch"}],"responses":{"201":{"description":"The claim result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Perk not found — No perk has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Perk not found","path":"/events/tickets/{ticketId}/perks/{perkId}/claim","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Records that a holder claimed a perk, so it cannot be claimed twice. The scan at the merchandise desk.\n\n#### Signature\n\n```http\nPOST /events/tickets/{ticketId}/perks/{perkId}/claim (ticketId: string, perkId: string) -> The claim result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Perk not found | No perk has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/perks/{perkId}/fulfill`"}},"/events/tickets/{ticketId}/perks/{perkId}/fulfill":{"post":{"operationId":"EventsController_fulfillPerk","summary":"Fulfil a perk","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"},{"name":"perkId","required":true,"in":"path","schema":{"type":"string"},"description":"Perk id.","example":"PRK-tshirt"}],"responses":{"201":{"description":"The fulfilment result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Perk not found — No perk has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Perk not found","path":"/events/tickets/{ticketId}/perks/{perkId}/fulfill","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Marks a claimed perk as actually handed over — the second half of claim, for perks collected later than they are claimed.\n\n#### Signature\n\n```http\nPOST /events/tickets/{ticketId}/perks/{perkId}/fulfill (ticketId: string, perkId: string) -> The fulfilment result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Perk not found | No perk has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/perks/{perkId}/claim`"}},"/events/tickets/{ticketId}/badge":{"get":{"operationId":"EventsController_generateBadge","summary":"Get a ticket badge","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"},{"name":"perkId","required":true,"in":"query","schema":{"type":"string"}},{"name":"claimIndex","required":true,"in":"query","schema":{"type":"string"}},{"name":"templateId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The badge","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/badge","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The printable badge for a ticket holder — name, role and access level.\n\n#### Signature\n\n```http\nGET /events/tickets/{ticketId}/badge (ticketId: string) -> The badge\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/{ticketId}/badge/printed`"}},"/events/tickets/{ticketId}/badge/printed":{"post":{"operationId":"EventsController_markBadgePrinted","summary":"Mark a badge as printed","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/tickets/{ticketId}/badge/printed","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Records that a badge has been printed, so registration desks do not print duplicates and reprints are visible.\n\n#### Signature\n\n```http\nPOST /events/tickets/{ticketId}/badge/printed (ticketId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/tickets/{ticketId}/badge`"}},"/events/{eventId}/ticket-types":{"get":{"operationId":"EventsController_getTicketTypes","summary":"Get ticket types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"Ticket types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The ticket types for an event — tiers, prices, sale windows and availability. What a purchase flow reads to build its options.\n\n#### Signature\n\n```http\nGET /events/{eventId}/ticket-types (eventId: string) -> Ticket types\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/tickets/purchase`"}},"/events/{eventId}/sessions":{"post":{"operationId":"EventsController_createSession","summary":"Create a session","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The created session","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Adds a session to an event — a talk, workshop or track slot within the programme.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/sessions (eventId: string, body) -> The created session\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/schedule`","requestBody":{"description":"The session to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Opening keynote","startDate":"2026-09-15T09:00:00.000Z","endDate":"2026-09-15T10:00:00.000Z","room":"Main hall"}}}}},"get":{"operationId":"EventsController_getEventSessions","summary":"List sessions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"day","required":true,"in":"query","schema":{"type":"string"}},{"name":"track","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Sessions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The sessions in an event's programme.\n\n#### Signature\n\n```http\nGET /events/{eventId}/sessions (eventId: string) -> Sessions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/schedule`"}},"/events/{eventId}/schedule":{"get":{"operationId":"EventsController_getSchedule","summary":"Get the event schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"The schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The programme arranged as a schedule — sessions ordered by time and room, ready to render as an agenda.\n\n#### Signature\n\n```http\nGET /events/{eventId}/schedule (eventId: string) -> The schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/sessions`"}},"/events/sessions/{sessionId}":{"put":{"operationId":"EventsController_updateSession","summary":"Update a session","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sessionId","required":true,"in":"path","schema":{"type":"string"},"description":"Session id.","example":"SES-4821"}],"responses":{"200":{"description":"The updated session","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Session not found — No session has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Session not found","path":"/events/sessions/{sessionId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Updates a session. Attendees who already planned around it are not notified — announce a moved session yourself.\n\n#### Signature\n\n```http\nPUT /events/sessions/{sessionId} (sessionId: string, body) -> The updated session\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Session not found | No session has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /events/sessions/{sessionId}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"room":"Auditorium B"}}}}},"delete":{"operationId":"EventsController_deleteSession","summary":"Delete a session","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"sessionId","required":true,"in":"path","schema":{"type":"string"},"description":"Session id.","example":"SES-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Session not found — No session has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Session not found","path":"/events/sessions/{sessionId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Removes a session from the programme.\n\n#### Signature\n\n```http\nDELETE /events/sessions/{sessionId} (sessionId: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Session not found | No session has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /events/sessions/{sessionId}`"}},"/events/checkin":{"post":{"operationId":"EventsController_checkIn","summary":"Check someone in","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The check-in result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"reason":{"type":"string","description":"Why the scan was refused.","example":"Ticket already used. Re-entry not allowed."},"checkIn":{"type":"object","additionalProperties":true},"ticket":{"type":"object","additionalProperties":true},"zoneOccupancy":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Admits a ticket holder by scanning their code, recording who validated it and where.\n\n`zone` and `checkpoint` record the physical location, and `sessionId` scopes the check-in to one session for events that track per-session attendance.\n\nThe validator is taken from the authenticated caller, falling back to `system` — so a shared scanner account produces an audit trail that cannot identify the person who scanned.\n\n**A refused scan is still a `201`** with `success: false` and a `reason` to show at the door — ticket not found or invalid, a different event, already used with re-entry off, re-entry limit reached, ticket type not accepted at the event's scan point for this `checkpoint`, a perk at that checkpoint not available on the ticket, or no access to this zone. Every refusal is logged as a denied check-in. Imported tickets are recognised by their code even without a signing secret.\n\n#### Signature\n\n```http\nPOST /events/checkin (body) -> The check-in result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Branch on `success`, not on the HTTP status.\n- Sign scanners in as identifiable users — the validator recorded is whoever the token belongs to.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/verify-scan`\n- `POST /events/checkout`","requestBody":{"description":"The scan.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"The scanned ticket code.","example":"EVT4821-9K2M4H"},"zone":{"type":"string","description":"Area being entered.","example":"main-hall"},"checkpoint":{"type":"string","description":"Which door or desk.","example":"north-entrance"},"sessionId":{"type":"string","description":"Session being entered, for per-session attendance.","example":"SES-4821"},"eventId":{"type":"string","description":"The event being scanned for; a ticket for another event is refused."},"validatorDevice":{"type":"string","description":"Scanner device id, recorded on the check-in."}}},"examples":{"door":{"summary":"General admission","value":{"code":"EVT4821-9K2M4H","zone":"main-hall","checkpoint":"north-entrance"}},"session":{"summary":"Into a specific session","value":{"code":"EVT4821-9K2M4H","sessionId":"SES-4821"}}}}}}}},"/events/verify-scan":{"post":{"operationId":"EventsController_verifyScan","summary":"Verify a scan without admitting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Verification result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Checks a scanned code and reports whether it would be admitted, **without recording a check-in**.\n\nUse it for a door display that shows the holder's name and access level before staff wave them through.\n\n#### Signature\n\n```http\nPOST /events/verify-scan (body) -> Verification result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Records nothing. Attendance is only counted by `checkin`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/checkin`","requestBody":{"description":"The scan to verify.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","example":"EVT4821-9K2M4H"},"zone":{"type":"string","example":"vip-lounge"},"checkpoint":{"type":"string","example":"north-entrance"}}},"example":{"code":"EVT4821-9K2M4H","zone":"vip-lounge"}}}}}},"/events/checkout":{"post":{"operationId":"EventsController_checkOut","summary":"Check someone out","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The check-out result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/events/checkout","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Records a holder leaving a zone — what keeps live occupancy accurate for venues with capacity limits.\n\n#### Signature\n\n```http\nPOST /events/checkout (body) -> The check-out result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Occupancy figures drift high if people leave without being checked out.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/occupancy`","requestBody":{"description":"Who is leaving, and from where.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["ticketId"],"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"zone":{"type":"string","example":"main-hall"}}},"example":{"ticketId":"TKT-9912","zone":"main-hall"}}}}}},"/events/{eventId}/occupancy":{"get":{"operationId":"EventsController_getZoneOccupancy","summary":"Get live occupancy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"Live occupancy","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"How many people are currently inside, by zone — the number a capacity limit is enforced against. Accurate only insofar as check-outs are recorded.\n\n#### Signature\n\n```http\nGET /events/{eventId}/occupancy (eventId: string) -> Live occupancy\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/checkout`"}},"/events/{eventId}/checkin-stats":{"get":{"operationId":"EventsController_getCheckInStats","summary":"Get check-in statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"day","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Check-in statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Attendance figures for an event — how many of those who bought actually turned up, and when they arrived.\n\n#### Signature\n\n```http\nGET /events/{eventId}/checkin-stats (eventId: string) -> Check-in statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/occupancy`"}},"/events/{eventId}/participants":{"post":{"operationId":"EventsController_createParticipant","summary":"Add a participant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The created participant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Event ID is required — The event id is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Event ID is required","path":"/events/{eventId}/participants","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Adds a participant — a speaker, sponsor, exhibitor or performer. Distinct from a ticket holder: participants are part of the programme rather than the audience.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/participants (eventId: string, body) -> The created participant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EVENT_ID_REQUIRED | Event ID is required | The event id is missing. | Supply it in the path. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/participants`","requestBody":{"description":"The participant to add.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"customer":"cus_4821","type":"speaker","role":"keynote","featured":true}}}}},"get":{"operationId":"EventsController_getEventParticipants","summary":"List participants","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"speaker"},{"name":"role","required":false,"in":"query","schema":{"type":"string"},"example":"keynote"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"confirmed"},{"name":"featured","required":false,"in":"query","schema":{"type":"boolean"},"description":"Only featured participants.","example":true},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"How many to return.","example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"description":"Offset paging — this module uses `offset`, not `page`.","example":0}],"responses":{"200":{"description":"Participants","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The event's participants, filterable by type, role, status and whether they are featured.\n\n#### Signature\n\n```http\nGET /events/{eventId}/participants (eventId: string, type?: string, role?: string, status?: string, featured?: boolean, limit?: integer, offset?: integer) -> Participants\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /events/participants/{participantId}`"}},"/events/participants/{participantId}":{"put":{"operationId":"EventsController_updateParticipant","summary":"Update a participant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"participantId","required":true,"in":"path","schema":{"type":"string"},"description":"Participant id.","example":"PRT-4821"}],"responses":{"200":{"description":"The updated participant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Participant not found — No participant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Participant not found","path":"/events/participants/{participantId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Updates a participant's details or role.\n\n#### Signature\n\n```http\nPUT /events/participants/{participantId} (participantId: string, body) -> The updated participant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Participant not found | No participant has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/participants/{participantId}/confirm`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"role":"panellist","featured":false}}}}},"delete":{"operationId":"EventsController_deleteParticipant","summary":"Delete a participant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"participantId","required":true,"in":"path","schema":{"type":"string"},"description":"Participant id.","example":"PRT-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Participant not found — No participant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Participant not found","path":"/events/participants/{participantId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Removes a participant entirely. Cancel instead to keep the record of who was booked.\n\n#### Signature\n\n```http\nDELETE /events/participants/{participantId} (participantId: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Participant not found | No participant has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/participants/{participantId}/cancel`"}},"/events/participants/{participantId}/confirm":{"post":{"operationId":"EventsController_confirmParticipant","summary":"Confirm a participant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"participantId","required":true,"in":"path","schema":{"type":"string"},"description":"Participant id.","example":"PRT-4821"}],"responses":{"201":{"description":"The confirmed participant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Participant not found — No participant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Participant not found","path":"/events/participants/{participantId}/confirm","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Confirms a participant's attendance — they have accepted and can be listed publicly.\n\n#### Signature\n\n```http\nPOST /events/participants/{participantId}/confirm (participantId: string) -> The confirmed participant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Participant not found | No participant has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/participants/{participantId}/cancel`"}},"/events/participants/{participantId}/cancel":{"post":{"operationId":"EventsController_cancelParticipant","summary":"Cancel a participant","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"participantId","required":true,"in":"path","schema":{"type":"string"},"description":"Participant id.","example":"PRT-4821"}],"responses":{"201":{"description":"The cancelled participant","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Participant not found — No participant has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Participant not found","path":"/events/participants/{participantId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Records that a participant has withdrawn. The record is kept, so the programme history shows who was booked.\n\n#### Signature\n\n```http\nPOST /events/participants/{participantId}/cancel (participantId: string) -> The cancelled participant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Participant not found | No participant has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /events/participants/{participantId}`"}},"/events/{eventId}/credentials/import":{"post":{"operationId":"EventsController_importCredentials","summary":"Import credentials","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The import result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Imports a list of credential codes — pre-printed wristbands or access cards produced by a third party — so they can be assigned to holders.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/credentials/import (eventId: string, body) -> The import result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/{eventId}/credentials/import-range`","requestBody":{"description":"The codes to import.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"codes":["WB-0001","WB-0002"]}}}}}},"/events/{eventId}/credentials/import-range":{"post":{"operationId":"EventsController_importCredentialRange","summary":"Import a credential range","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The import result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Imports a contiguous range of credential codes by prefix and bounds, rather than listing each one — for a sequential batch of wristbands.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/credentials/import-range (eventId: string, body) -> The import result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Check the range before importing — a wrong bound creates thousands of unusable credentials.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/{eventId}/credentials/import`","requestBody":{"description":"The range to import.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"prefix":"WB-","from":1,"to":500,"padding":4}}}}}},"/events/credentials/assign":{"post":{"operationId":"EventsController_assignCredential","summary":"Assign a credential","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The assignment result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Links an imported credential code to a ticket or participant, so scanning the wristband identifies the holder.\n\n#### Signature\n\n```http\nPOST /events/credentials/assign (body) -> The assignment result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/credentials/{code}/revoke`","requestBody":{"description":"What to assign to whom.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"WB-0001","ticketId":"TKT-9912"}}}}}},"/events/credentials/{code}/revoke":{"post":{"operationId":"EventsController_revokeCredential","summary":"Revoke a credential","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"path","schema":{"type":"string"},"description":"Credential code.","example":"WB-0001"}],"responses":{"201":{"description":"The revocation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Credential not found — No credential has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Credential not found","path":"/events/credentials/{code}/revoke","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Invalidates a credential — lost, stolen, or handed to the wrong person. It stops working at every checkpoint immediately.\n\n#### Signature\n\n```http\nPOST /events/credentials/{code}/revoke (code: string) -> The revocation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Credential not found | No credential has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/credentials/assign`"}},"/events/{eventId}/credentials":{"get":{"operationId":"EventsController_getCredentials","summary":"List credentials","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"batchId","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Credentials","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The credentials imported for an event, and who each is assigned to.\n\n#### Signature\n\n```http\nGET /events/{eventId}/credentials (eventId: string) -> Credentials\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/credentials/assign`"}},"/events/{eventId}/media/upload":{"post":{"operationId":"EventsController_uploadEventMedia","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The uploaded media","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"summary":"Upload event media","description":"Uploads photos or video to an event's shared gallery.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/media/upload (eventId: string, body) -> The uploaded media\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /events/{eventId}/media/upload/{guestId}`","requestBody":{"description":"Multipart form with the media.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/events/{eventId}/media/upload/{guestId}":{"post":{"operationId":"EventsController_uploadGuestMedia","summary":"Upload media as a guest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"guestId","required":true,"in":"path","schema":{"type":"string"},"description":"Guest identifier.","example":"GST-4821"}],"responses":{"201":{"description":"The uploaded media","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Uploads media attributed to a specific guest — how a shared event gallery collects photos from attendees and keeps track of who contributed what.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/media/upload/{guestId} (eventId: string, guestId: string, body) -> The uploaded media\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/media/guest/{guestId}`","requestBody":{"description":"Multipart form with the media.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/events/{eventId}/media":{"get":{"operationId":"EventsController_getEventMedia","summary":"Get event media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"page","required":true,"in":"query","schema":{"type":"number"}},{"name":"signed","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Event media","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"All media uploaded for an event.\n\n#### Signature\n\n```http\nGET /events/{eventId}/media (eventId: string) -> Event media\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/media/guests`"}},"/events/{eventId}/media/guest/{guestId}":{"get":{"operationId":"EventsController_getGuestMedia","summary":"Get a guest's media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"guestId","required":true,"in":"path","schema":{"type":"string"},"description":"Guest identifier.","example":"GST-4821"},{"name":"page","required":true,"in":"query","schema":{"type":"number"}},{"name":"signed","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The guest's media","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The media one guest contributed.\n\n#### Signature\n\n```http\nGET /events/{eventId}/media/guest/{guestId} (eventId: string, guestId: string) -> The guest's media\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/media`"}},"/events/{eventId}/media/guests":{"get":{"operationId":"EventsController_getEventGuests","summary":"List media contributors","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"Contributors","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"The guests who have uploaded media to an event.\n\n#### Signature\n\n```http\nGET /events/{eventId}/media/guests (eventId: string) -> Contributors\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/media/guest/{guestId}`"}},"/events/{eventId}/media/share":{"post":{"operationId":"EventsController_shareMedia","summary":"Share event media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The share result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events"],"description":"Shares event media — generating a link or distributing it to attendees.\n\nPhotos of attendees are personal data, and guests uploading to a shared gallery may not expect wider distribution. Check what consent was given before sharing beyond the event.\n\n#### Signature\n\n```http\nPOST /events/{eventId}/media/share (eventId: string, body) -> The share result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Attendee photos are personal data — confirm consent before distributing them.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /events/{eventId}/media`","requestBody":{"description":"What to share, and with whom.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"mediaIds":["MED-4821"],"recipients":["ada@example.com"]}}}}}},"/client/events":{"get":{"operationId":"EventsClientController_getEvents","summary":"Browse events","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"conference"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-12-31"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"description":"Offset paging.","example":0}],"responses":{"200":{"description":"Published events","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The public event listing — what an attendee sees. Only events that have been published appear here.\n\n#### Signature\n\n```http\nGET /client/events (type?: string, fromDate?: string, toDate?: string, limit?: integer, offset?: integer) -> Published events\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/events/{eventId}`"}},"/client/events/tickets/mine":{"get":{"operationId":"EventsClientController_getMyTickets","summary":"Get my tickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one event.","example":"EVT-4821"}],"responses":{"200":{"description":"The attendee's tickets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"holderEmail":{"type":"string","example":"ada@example.com"},"status":{"type":"string","example":"active"},"code":{"type":"string","description":"Scannable code — the credential that admits the holder.","example":"EVT4821-9K2M4H"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The signed-in attendee's tickets, optionally narrowed to one event.\n\nDeclared **before** `GET /client/events/{eventId}` so the bare `:eventId` route does not swallow it — the controller notes this explicitly, and reordering the handlers would break it.\n\n#### Signature\n\n```http\nGET /client/events/tickets/mine (eventId?: string) -> The attendee's tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Scoped to the caller — an anonymous request returns nothing useful.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/booking`"}},"/client/events/booking":{"get":{"operationId":"EventsClientController_getBooking","summary":"Look up a booking","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"query","schema":{"type":"string"},"description":"Buyer email.","example":"ada@example.com"},{"name":"bookingId","required":true,"in":"query","schema":{"type":"string"},"description":"Booking id from the confirmation.","example":"BKG-4821"}],"responses":{"200":{"description":"The booking and its tickets","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Booking not found — No booking matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/events/booking","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Retrieves a booking from the buyer's email plus the booking id — the \"find my tickets\" flow for someone who bought without an account.\n\nThose two values are the only credential, so anyone holding both can read the booking. Rate-limit any public form built on it.\n\n#### Signature\n\n```http\nGET /client/events/booking (email?: string, bookingId?: string) -> The booking and its tickets\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated. Email plus booking id is enough to read someone's tickets.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Booking not found | No booking matches. | Check the identifier. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/events/tickets/{ticketId}`"}},"/client/events/{eventId}":{"get":{"operationId":"EventsClientController_getEvent","summary":"Get an event","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"The event","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Event not found — No event matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Event not found","path":"/client/events/{eventId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The public detail page for one event.\n\nThis route is declared **after** `tickets/mine` and `booking` deliberately: Nest matches in registration order, so a bare `:eventId` placed earlier would swallow those paths.\n\n#### Signature\n\n```http\nGET /client/events/{eventId} (eventId: string) -> The event\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Event not found | No event matches. | Check the identifier. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/events/{eventId}/ticket-types`"}},"/client/events/tickets/{ticketId}":{"get":{"operationId":"EventsClientController_getTicket","summary":"Get a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"200":{"description":"The ticket","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"holderEmail":{"type":"string","example":"ada@example.com"},"status":{"type":"string","example":"active"},"code":{"type":"string","description":"Scannable code — the credential that admits the holder.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/client/events/tickets/{ticketId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Fetches one of the caller's tickets. Only the holder gets it: a ticket whose `holderEmail` is not the signed-in customer's answers `404`, the same as a missing one.\n\n#### Signature\n\n```http\nGET /client/events/tickets/{ticketId} (ticketId: string) -> The ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket matches. | Check the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/tickets/{ticketId}/qr`"}},"/client/events/tickets/{ticketId}/qr":{"get":{"operationId":"EventsClientController_getTicketQR","summary":"Get a ticket QR code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"200":{"description":"The ticket and its QR payload","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"holderEmail":{"type":"string","example":"ada@example.com"},"status":{"type":"string","example":"active"},"code":{"type":"string","description":"Scannable code — the credential that admits the holder.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/client/events/tickets/{ticketId}/qr","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Returns the scannable QR payload for one of the caller's tickets — what the holder shows at the door. **The payload admits whoever presents it**, so it is only returned to the ticket's holder; anyone else gets `404`.\n\n#### Signature\n\n```http\nGET /client/events/tickets/{ticketId}/qr (ticketId: string) -> The ticket and its QR payload\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket matches. | Check the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/tickets/{ticketId}`"}},"/client/events/tickets/{ticketId}/transfer":{"post":{"operationId":"EventsClientController_transferTicket","summary":"Transfer a ticket","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"201":{"description":"The transferred ticket","content":{"application/json":{"schema":{"type":"object","description":"An event ticket.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"ticketId":{"type":"string","example":"TKT-9912"},"eventId":{"type":"string","example":"EVT-4821"},"holderEmail":{"type":"string","example":"ada@example.com"},"status":{"type":"string","example":"active"},"code":{"type":"string","description":"Scannable code — the credential that admits the holder.","example":"EVT4821-9K2M4H"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/client/events/tickets/{ticketId}/transfer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Transfers one of the caller's tickets to someone else — the attendee-initiated handover when they can no longer attend. Only the current holder may; anyone else gets `404`.\n\n#### Signature\n\n```http\nPOST /client/events/tickets/{ticketId}/transfer (ticketId: string, body) -> The transferred ticket\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket matches. | Check the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/tickets/{ticketId}`","requestBody":{"description":"The new holder.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"holderEmail":"grace@example.com","holderName":"Grace Hopper"}}}}}},"/client/events/tickets/{ticketId}/perks":{"get":{"operationId":"EventsClientController_getTicketPerks","summary":"Get ticket perks","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ticketId","required":true,"in":"path","schema":{"type":"string"},"description":"Ticket id.","example":"TKT-9912"}],"responses":{"200":{"description":"The ticket's perks","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket not found — No ticket matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket not found","path":"/client/events/tickets/{ticketId}/perks","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The perks included with a ticket and which have been claimed — what the holder is entitled to.\n\n#### Signature\n\n```http\nGET /client/events/tickets/{ticketId}/perks (ticketId: string) -> The ticket's perks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket not found | No ticket matches. | Check the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/tickets/{ticketId}`"}},"/client/events/{eventId}/ticket-types":{"get":{"operationId":"EventsClientController_getTicketTypes","summary":"Get ticket types","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"Ticket types","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The ticket tiers on sale for an event, with prices and availability — what a purchase form is built from.\n\n#### Signature\n\n```http\nGET /client/events/{eventId}/ticket-types (eventId: string) -> Ticket types\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/events/tickets/purchase`"}},"/client/events/tickets/purchase":{"post":{"operationId":"EventsClientController_purchaseTickets","summary":"Purchase tickets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-client-host","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"201":{"description":"The booking and its tickets","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"<ticket type> is not available / is not on sale yet / sale has ended — The ticket type is inactive or outside its sale window.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"<ticket type> is not available / is not on sale yet / sale has ended","path":"/client/events/tickets/purchase","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Ticket type not found — No ticket type matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket type not found","path":"/client/events/tickets/purchase","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The public purchase path. Availability, sale windows and per-type limits are all validated here, so a ticket type that looked available when the page loaded can still be refused.\n\nPayment can be supplied inline via `paymentRef`, or the purchase can be created first and confirmed afterwards with `confirm-order` once the gateway settles.\n\n#### Signature\n\n```http\nPOST /client/events/tickets/purchase (body) -> The booking and its tickets\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated. The `email` supplied becomes the only key to the booking afterwards.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket type not found | No ticket type matches. | Check the identifier. |\n| `400` | NOT_AVAILABLE | <ticket type> is not available / is not on sale yet / sale has ended | The ticket type is inactive or outside its sale window. | The message names the ticket type and the reason — show it to the buyer. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/events/tickets/confirm-order`\n- `GET /client/events/booking`","requestBody":{"description":"What to buy, and for whom.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["eventId","email","items"],"properties":{"eventId":{"type":"string","example":"EVT-4821"},"email":{"type":"string","description":"Buyer email. Becomes the key for retrieving the booking later.","example":"ada@example.com"},"name":{"type":"string","example":"Ada Lovelace"},"items":{"type":"array","items":{"type":"object","properties":{"ticketTypeId":{"type":"string","example":"TT-general"},"quantity":{"type":"number","example":2}}}},"paymentMethod":{"type":"string","example":"card"},"paymentRef":{"type":"string","description":"Reference for a payment already taken.","example":"pi_3PabcXYZ"},"paymentGateway":{"type":"string","example":"stripe"},"promoCode":{"type":"string","example":"EARLYBIRD"}}},"examples":{"paid":{"summary":"Purchase with payment already taken","value":{"eventId":"EVT-4821","email":"ada@example.com","name":"Ada Lovelace","items":[{"ticketTypeId":"TT-general","quantity":2}],"paymentRef":"pi_3PabcXYZ","paymentGateway":"stripe"}},"deferred":{"summary":"Create the booking, confirm payment after","value":{"eventId":"EVT-4821","email":"ada@example.com","items":[{"ticketTypeId":"TT-general","quantity":2}]}}}}}}}},"/client/events/tickets/confirm-order":{"post":{"operationId":"EventsClientController_confirmOrder","summary":"Confirm a ticket order","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The confirmed booking","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Booking not found — No booking matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Booking not found","path":"/client/events/tickets/confirm-order","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Confirms payment against a booking created earlier — the second half of a redirect-based checkout, called when the customer returns from the gateway.\n\nThe payment reference is taken on trust from an unauthenticated caller, so verify it with the provider before relying on it.\n\n#### Signature\n\n```http\nPOST /client/events/tickets/confirm-order (body) -> The confirmed booking\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated and unverified — confirm with the gateway rather than trusting the reference.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Booking not found | No booking matches. | Check the identifier. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/events/tickets/purchase`","requestBody":{"description":"The booking and the payment that settled it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["bookingId","paymentRef"],"properties":{"bookingId":{"type":"string","example":"BKG-4821"},"paymentRef":{"type":"string","example":"pi_3PabcXYZ"},"paymentGateway":{"type":"string","example":"stripe"},"paymentMethod":{"type":"string","example":"card"}}},"example":{"bookingId":"BKG-4821","paymentRef":"pi_3PabcXYZ","paymentGateway":"stripe"}}}}}},"/client/events/stripe/config":{"get":{"operationId":"EventsClientController_getStripeConfig","summary":"Get the Stripe configuration","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Stripe client configuration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The public Stripe configuration a browser needs to render payment elements. Contains no secrets.\n\n#### Signature\n\n```http\nGET /client/events/stripe/config () -> Stripe client configuration\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/events/stripe/intent`"}},"/client/events/stripe/intent":{"post":{"operationId":"EventsClientController_stripePaymentIntent","summary":"Create a Stripe PaymentIntent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The PaymentIntent, including `client_secret`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Creates a PaymentIntent for a ticket purchase and returns its client secret for the browser SDK to confirm.\n\n#### Signature\n\n```http\nPOST /client/events/stripe/intent (body) -> The PaymentIntent, including `client_secret`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — validate the amount server-side against the ticket types rather than trusting the client.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/events/tickets/confirm-order`","requestBody":{"description":"Intent details.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"eventId":"EVT-4821","amount":9000,"currency":"USD","email":"ada@example.com"}}}}}},"/client/events/stripe/checkout-session":{"post":{"operationId":"EventsClientController_stripeCheckoutSession","summary":"Create a Stripe Checkout session","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The session, including its redirect URL","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Creates a hosted Stripe Checkout session for a ticket purchase and returns the URL to redirect the buyer to.\n\n#### Signature\n\n```http\nPOST /client/events/stripe/checkout-session (body) -> The session, including its redirect URL\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/events/stripe/config`","requestBody":{"description":"Session details.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"eventId":"EVT-4821","items":[{"ticketTypeId":"TT-general","quantity":2}],"successUrl":"https://events.example.com/thanks","cancelUrl":"https://events.example.com/tickets"}}}}}},"/client/events/tickets/register":{"post":{"operationId":"EventsClientController_registerTickets","summary":"Register for an event","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The registration and its tickets","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Ticket type not found — No ticket type matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Ticket type not found","path":"/client/events/tickets/register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Registration for free events — issues tickets without a payment step. Use `purchase` where money is involved.\n\n#### Signature\n\n```http\nPOST /client/events/tickets/register (body) -> The registration and its tickets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Unauthenticated — rate-limit it, or a free event can be filled with fake registrations.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Ticket type not found | No ticket type matches. | Check the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/events/tickets/purchase`","requestBody":{"description":"Who is registering.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"eventId":"EVT-4821","email":"ada@example.com","name":"Ada Lovelace","items":[{"ticketTypeId":"TT-free","quantity":1}]}}}}}},"/client/events/{eventId}/sessions":{"get":{"operationId":"EventsClientController_getEventSessions","summary":"Get event sessions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"day","required":true,"in":"query","schema":{"type":"string"}},{"name":"track","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Sessions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The sessions in an event's public programme.\n\n#### Signature\n\n```http\nGET /client/events/{eventId}/sessions (eventId: string) -> Sessions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/events/{eventId}/schedule`"}},"/client/events/{eventId}/schedule":{"get":{"operationId":"EventsClientController_getSchedule","summary":"Get the event schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"200":{"description":"The schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The programme as an agenda, ordered by time and room.\n\n#### Signature\n\n```http\nGET /client/events/{eventId}/schedule (eventId: string) -> The schedule\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/events/{eventId}/sessions`"}},"/client/events/{eventId}/participants":{"get":{"operationId":"EventsClientController_getEventParticipants","summary":"Get event participants","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"role","required":true,"in":"query","schema":{"type":"string"}},{"name":"featured","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Participants","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The publicly listed speakers, sponsors and exhibitors. Only confirmed participants appear.\n\n#### Signature\n\n```http\nGET /client/events/{eventId}/participants (eventId: string) -> Participants\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/participation/mine`"}},"/client/events/participation/mine":{"get":{"operationId":"EventsClientController_getMyParticipation","summary":"Get my participation invitations","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Participation records","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The events the caller has been invited to take part in as a speaker, sponsor or exhibitor — their side of the programme.\n\n#### Signature\n\n```http\nGET /client/events/participation/mine () -> Participation records\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/events/participation/{participantId}/respond`"}},"/client/events/participation/{participantId}/respond":{"put":{"operationId":"EventsClientController_respondToInvite","summary":"Respond to a participation invitation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"participantId","required":true,"in":"path","schema":{"type":"string"},"description":"Participation record id.","example":"PRT-4821"}],"responses":{"200":{"description":"The updated participation record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Participant not found — No participant matches.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Participant not found","path":"/client/events/participation/{participantId}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Accepts or declines an invitation to take part. Accepting confirms the participant, which is what makes them appear in the public listing.\n\n#### Signature\n\n```http\nPUT /client/events/participation/{participantId}/respond (participantId: string, body) -> The updated participation record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Participant not found | No participant matches. | Check the identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/participation/mine`","requestBody":{"description":"The response.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"response":{"type":"string","enum":["accept","decline"],"example":"accept"},"note":{"type":"string"}}},"example":{"response":"accept"}}}}}},"/client/events/{eventId}/media":{"get":{"operationId":"EventsClientController_getEventMedia","summary":"Get event media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"page","required":true,"in":"query","schema":{"type":"number"}},{"name":"signed","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Event media","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The publicly visible media for an event — the shared gallery attendees can browse.\n\n#### Signature\n\n```http\nGET /client/events/{eventId}/media (eventId: string) -> Event media\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/events/{eventId}/media/upload`"}},"/client/events/{eventId}/media/guest/{guestId}":{"get":{"operationId":"EventsClientController_getGuestMedia","summary":"Get a guest's media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"guestId","required":true,"in":"path","schema":{"type":"string"},"description":"Guest identifier.","example":"GST-4821"},{"name":"page","required":true,"in":"query","schema":{"type":"number"}},{"name":"signed","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The guest's media","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"The media one guest contributed to an event gallery.\n\n#### Signature\n\n```http\nGET /client/events/{eventId}/media/guest/{guestId} (eventId: string, guestId: string) -> The guest's media\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/events/{eventId}/media`"}},"/client/events/{eventId}/media/upload":{"post":{"operationId":"EventsClientController_uploadEventMedia","summary":"Upload media to an event","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"}],"responses":{"201":{"description":"The uploaded media","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Uploads a photo or video to an event's shared gallery.\n\nNeeds a signed-in caller. Anything accepted here becomes part of the event gallery, so moderate what is uploaded.\n\n#### Signature\n\n```http\nPOST /client/events/{eventId}/media/upload (eventId: string, body) -> The uploaded media\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/events/{eventId}/media/upload/{guestId}`","requestBody":{"description":"Multipart form with the media.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/client/events/{eventId}/media/upload/{guestId}":{"post":{"operationId":"EventsClientController_uploadGuestMedia","summary":"Upload media as a guest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"eventId","required":true,"in":"path","schema":{"type":"string"},"description":"Event id.","example":"EVT-4821"},{"name":"guestId","required":true,"in":"path","schema":{"type":"string"},"description":"Guest identifier.","example":"GST-4821"}],"responses":{"201":{"description":"The uploaded media","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Events · Attendee"],"description":"Uploads media attributed to a named guest, so a shared gallery can show who contributed what.\n\n#### Signature\n\n```http\nPOST /client/events/{eventId}/media/upload/{guestId} (eventId: string, guestId: string, body) -> The uploaded media\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The guest id is not verified — attribution is on trust.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/events/{eventId}/media/guest/{guestId}`","requestBody":{"description":"Multipart form with the media.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/affiliate/programs":{"post":{"operationId":"AffiliateController_createProgram","summary":"Create an affiliate program","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created program","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Program name is required — `name` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Program name is required","path":"/affiliate/programs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Creates a program. The name is its identifier and must be unique — a duplicate is refused rather than silently overwriting the existing program and its commission terms.\n\n#### Signature\n\n```http\nPOST /affiliate/programs (body) -> The created program\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAME_REQUIRED | Program name is required | `name` is missing. | The name is the program identifier. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/affiliates`","requestBody":{"description":"The program to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"partner-2026","commissionType":"percentage","commissionValue":10,"cookieDays":30}}}}},"get":{"operationId":"AffiliateController_listPrograms","summary":"List affiliate programs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by status."},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by type."}],"responses":{"200":{"description":"Programs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"The affiliate programs defined for the org, with their commission structures.\n\n#### Signature\n\n```http\nGET /affiliate/programs (status?: string, type?: string) -> Programs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/programs/{name}`"}},"/affiliate/programs/{name}":{"get":{"operationId":"AffiliateController_getProgram","summary":"Get an affiliate program","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Program name.","example":"partner-2026"}],"responses":{"200":{"description":"The program","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Program not found — No affiliate program has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Program not found","path":"/affiliate/programs/{name}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Fetches one program with its commission rules and terms.\n\n#### Signature\n\n```http\nGET /affiliate/programs/{name} (name: string) -> The program\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROGRAM_NOT_FOUND | Program not found | No affiliate program has that name. | List programs with `GET /affiliate/programs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /affiliate/programs/{name}`"},"put":{"operationId":"AffiliateController_updateProgram","summary":"Update an affiliate program","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"name","required":true,"in":"path","schema":{"type":"string"},"description":"Program name.","example":"partner-2026"}],"responses":{"200":{"description":"The updated program","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Program not found — No affiliate program has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Program not found","path":"/affiliate/programs/{name}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Updates a program. Changing commission terms affects **future** referrals — those already tracked keep the rate they were recorded at, which is what stops a rate change retroactively altering what affiliates are owed.\n\n#### Signature\n\n```http\nPUT /affiliate/programs/{name} (name: string, body) -> The updated program\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing referrals keep their recorded commission.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROGRAM_NOT_FOUND | Program not found | No affiliate program has that name. | List programs with `GET /affiliate/programs`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /affiliate/affiliates/{id}/commission-override`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"commissionValue":12}}}}}},"/affiliate/affiliates":{"post":{"operationId":"AffiliateController_registerAffiliate","summary":"Create an affiliate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An affiliate with this email already exists in this program — That email is already enrolled in the program.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An affiliate with this email already exists in this program","path":"/affiliate/affiliates","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Program not found — No affiliate program has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Program not found","path":"/affiliate/affiliates","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Failed to generate unique code — A unique referral code could not be produced after repeated attempts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Failed to generate unique code","path":"/affiliate/affiliates","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Affiliate"],"description":"Enrols an affiliate in a program and issues their referral code. One affiliate per email per program — the same person can join different programs, but not the same one twice.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates (body) -> The created affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROGRAM_NOT_FOUND | Program not found | No affiliate program has that name. | List programs with `GET /affiliate/programs`. |\n| `400` | AFFILIATE_EXISTS | An affiliate with this email already exists in this program | That email is already enrolled in the program. | Look up the existing affiliate rather than creating a second. |\n| `500` | CODE_GENERATION_FAILED | Failed to generate unique code | A unique referral code could not be produced after repeated attempts. | Retry. Persistent failure suggests the code space is exhausted or a collision check is misbehaving. |\n\nPlus the standard platform errors: `401`, `403`, `429`.\n\n#### See also\n\n- `POST /affiliate/affiliates/{id}/approve`","requestBody":{"description":"The affiliate to enrol.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"partner@example.com","name":"Grace Hopper","program":"partner-2026"}}}}},"get":{"operationId":"AffiliateController_listAffiliates","summary":"List affiliates","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"active"},{"name":"program","required":false,"in":"query","schema":{"type":"string"},"example":"partner-2026"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by type."},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."},{"name":"pageSize","required":false,"in":"query","schema":{"type":"string"},"description":"Rows per page."}],"responses":{"200":{"description":"Affiliates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Affiliates across programs, with their status and codes.\n\n#### Signature\n\n```http\nGET /affiliate/affiliates (type?: string, page?: string, pageSize?: string, program?: string, status?: string) -> Affiliates\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/affiliates/{id}`"}},"/affiliate/affiliates/{id}":{"get":{"operationId":"AffiliateController_getAffiliate","summary":"Get an affiliate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Fetches one affiliate with their code, program and commission arrangement.\n\n#### Signature\n\n```http\nGET /affiliate/affiliates/{id} (id: string) -> The affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/affiliates/{id}/approve`"}},"/affiliate/affiliates/{id}/approve":{"post":{"operationId":"AffiliateController_approveAffiliate","summary":"Approve an affiliate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The approved affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Affiliate is already active — The affiliate is already approved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Affiliate is already active","path":"/affiliate/affiliates/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Activates an affiliate so their referrals start earning. An already-active affiliate is refused rather than silently re-approved.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates/{id}/approve (id: string) -> The approved affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n| `400` | ALREADY_ACTIVE | Affiliate is already active | The affiliate is already approved. | Not idempotent — read the status first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/affiliates/{id}/suspend`"}},"/affiliate/affiliates/{id}/reject":{"post":{"operationId":"AffiliateController_rejectAffiliate","summary":"Decline an affiliate application","description":"Closes an application that never started (different from suspending a working account); the applicant is told in those words. The record and the `reason` are kept.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates/{id}/reject (id: string, body) -> The declined affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n| `400` | ALREADY_ACTIVE | This affiliate is already active — suspend them instead of rejecting the application | The affiliate is active. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/affiliates/{id}/approve`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The declined affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This affiliate is already active — suspend them instead of rejecting the application — The affiliate is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This affiliate is already active — suspend them instead of rejecting the application","path":"/affiliate/affiliates/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/reject","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Audience does not match our products"}}}}}},"/affiliate/affiliates/{id}/suspend":{"post":{"operationId":"AffiliateController_suspendAffiliate","summary":"Suspend an affiliate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The suspended affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Affiliate is not active (status: <status>) — The affiliate is not currently active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Affiliate is not active (status: <status>)","path":"/affiliate/affiliates/{id}/suspend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/suspend","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Suspends an affiliate, stopping new referrals from being attributed to them. Referrals already tracked and approved are unaffected — suspension stops future earning, it does not claw back past commission.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates/{id}/suspend (id: string) -> The suspended affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n| `400` | NOT_ACTIVE | Affiliate is not active (status: <status>) | The affiliate is not currently active. | The message names the current status. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/affiliates/{id}/reactivate`"}},"/affiliate/affiliates/{id}/reactivate":{"post":{"operationId":"AffiliateController_reactivateAffiliate","summary":"Reactivate an affiliate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The reactivated affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/reactivate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Restores a suspended affiliate to active, allowing new referrals to be attributed again.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates/{id}/reactivate (id: string) -> The reactivated affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/affiliates/{id}/suspend`"}},"/affiliate/affiliates/{id}/commission-override":{"put":{"operationId":"AffiliateController_setCommissionOverride","summary":"Override an affiliate's commission","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The updated affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/commission-override","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Sets a commission rate for one affiliate that differs from their program's — a negotiated rate for a large partner. `type` chooses between a flat amount and a percentage.\n\n#### Signature\n\n```http\nPUT /affiliate/affiliates/{id}/commission-override (id: string, body) -> The updated affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Applies to future referrals; existing ones keep their recorded rate.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /affiliate/programs/{name}`","requestBody":{"description":"The override.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","value"],"properties":{"type":{"type":"string","enum":["flat","percentage"],"example":"percentage"},"value":{"type":"number","description":"A percentage, or a flat amount, per `type`.","example":15}}},"example":{"type":"percentage","value":15}}}}}},"/affiliate/affiliates/{id}/codes":{"post":{"operationId":"AffiliateController_addAffiliateCode","summary":"Add a referral code to an affiliate","description":"Gives the affiliate a code they chose. Additive — an affiliate can hold several live codes. By default the new code becomes the main one (shown to them and used in generated links) and the code it replaces is retired, not deleted, so links already out keep crediting them. `makePrimary: false` adds without changing the main code. Codes are matched case-insensitively and stored as typed.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates/{id}/codes (id: string, body) -> The affiliate, with the new code in `codes[]`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n| `400` | CODE_REQUIRED | A referral code is required | `code` is empty. | — |\n| `409` | CODE_TAKEN | \"<code>\" is already taken by <name> | Another affiliate holds it (a retired one: \"was used by <name> and still points at them\"). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/codes/check`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The affiliate, with the new code in `codes[]`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A referral code is required — `code` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A referral code is required","path":"/affiliate/affiliates/{id}/codes","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/codes","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"\"<code>\" is already taken by <name> — Another affiliate holds it (a retired one: \"was used by <name> and still points at them\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"\"<code>\" is already taken by <name>","path":"/affiliate/affiliates/{id}/codes","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"},"label":{"type":"string"},"makePrimary":{"type":"boolean","default":true}}},"example":{"code":"sarah","label":"Personal"}}}}}},"/affiliate/affiliates/{id}/codes/send":{"post":{"operationId":"AffiliateController_sendAffiliateCode","summary":"Send an affiliate their code","description":"Emails and texts the affiliate their current code and referral link; nothing changes. The link is built on the site the org publishes on, never from the caller's host; with no site the code is still sent and `linkUnavailable` says why. Optional `productSlug`, `page`, `url` or `siteName` aim the link; `code` sends one of their other codes instead of the main one.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates/{id}/codes/send (id: string, body) -> { affiliate, code, link, linkUnavailable? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n| `409` | NO_CODE | This affiliate has no code to send yet — issue one first. | The affiliate has no code. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"{ affiliate, code, link, linkUnavailable? }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/codes/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This affiliate has no code to send yet — issue one first. — The affiliate has no code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This affiliate has no code to send yet — issue one first.","path":"/affiliate/affiliates/{id}/codes/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"productSlug":{"type":"string"},"page":{"type":"string"},"url":{"type":"string"},"siteName":{"type":"string"}}},"example":{"productSlug":"trail-shoe"}}}}}},"/affiliate/affiliates/{id}/codes/reset":{"post":{"operationId":"AffiliateController_resetAffiliateCode","summary":"Issue a new code","description":"Generates a fresh code, makes it the main one and retires the one it replaces (links already shared keep crediting them). The affiliate is emailed and texted the new code and link.\n\n#### Signature\n\n```http\nPOST /affiliate/affiliates/{id}/codes/reset (id: string, body) -> { affiliate, code, replacedCode, link }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"{ affiliate, code, replacedCode, link }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/codes/reset","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string"}}}}}}}},"/affiliate/affiliates/{id}/codes/primary":{"put":{"operationId":"AffiliateController_setPrimaryCode","summary":"Choose the main code","description":"The main code is what the affiliate is shown and what generated links carry. Promoting a retired code brings it back into circulation.\n\n#### Signature\n\n```http\nPUT /affiliate/affiliates/{id}/codes/primary (id: string, body) -> The affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n| `400` | CODE_DISABLED | \"<code>\" is disabled — turn it back on before making it the main code | The code is disabled. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"\"<code>\" is disabled — turn it back on before making it the main code — The code is disabled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"\"<code>\" is disabled — turn it back on before making it the main code","path":"/affiliate/affiliates/{id}/codes/primary","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/codes/primary","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}},"example":{"code":"sarah"}}}}}},"/affiliate/affiliates/{id}/codes/status":{"put":{"operationId":"AffiliateController_setCodeStatus","summary":"Retire, disable or reactivate a code","description":"`retired` stops advertising a code but keeps crediting links already out; `disabled` stops it resolving at all (a leaked or abused code); `active` restores it. The main code cannot be stood down — promote another first.\n\n#### Signature\n\n```http\nPUT /affiliate/affiliates/{id}/codes/status (id: string, body) -> The affiliate\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |\n| `400` | MAIN_CODE | \"<code>\" is this affiliate's main code. Make another one their main code first, otherwise they have nothing to hand out. | Retiring or disabling the main code. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The affiliate","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"\"<code>\" is this affiliate's main code. Make another one their main code first, otherwise they have nothing to hand out. — Retiring or disabling the main code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"\"<code>\" is this affiliate's main code. Make another one their main code first, otherwise they have nothing to hand out.","path":"/affiliate/affiliates/{id}/codes/status","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Affiliate not found — No affiliate has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Affiliate not found","path":"/affiliate/affiliates/{id}/codes/status","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","status"],"properties":{"code":{"type":"string"},"status":{"type":"string","enum":["active","retired","disabled"]}}},"example":{"code":"spring-promo","status":"retired"}}}}}},"/affiliate/codes/check":{"get":{"operationId":"AffiliateController_checkAffiliateCode","summary":"Check a referral code","description":"Answers before anything is written, so a form can say \"taken\" while someone types. Never an error: a code that cannot be used returns `available: false` with the reason. Pass `affiliateId` when adding to an existing affiliate so their own codes do not collide.\n\n#### Signature\n\n```http\nGET /affiliate/codes/check (code?: string, affiliateId?: string) -> { available, code, reason? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"query","schema":{"type":"string"},"example":"sarah"},{"name":"affiliateId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ available, code, reason? }","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean"},"code":{"type":"string"},"reason":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"]}},"/affiliate/links/generate":{"post":{"operationId":"AffiliateController_generateLink","summary":"Generate an affiliate link","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The tracking link","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"No site found. Provide a url or siteName. — No destination site could be resolved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No site found. Provide a url or siteName.","path":"/affiliate/links/generate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Builds a tracking link for an affiliate, optionally pointing at a specific product or page. Identify the affiliate by `code` or `affiliateId`; a destination comes from `url`, `productSlug` or `page`.\n\n#### Signature\n\n```http\nPOST /affiliate/links/generate (body) -> The tracking link\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NO_SITE | No site found. Provide a url or siteName. | No destination site could be resolved. | Supply a `url`, or configure a default site. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/public/resolve/{code}`","requestBody":{"description":"What link to build.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"code":{"type":"string","example":"GRACE10"},"affiliateId":{"type":"string"},"customerId":{"type":"string"},"url":{"type":"string","example":"https://shop.example.com/"},"productSlug":{"type":"string","example":"cola-330ml"},"page":{"type":"string","example":"pricing"}}},"example":{"code":"GRACE10","productSlug":"cola-330ml"}}}}}},"/affiliate/referrals":{"get":{"operationId":"AffiliateController_listReferrals","summary":"List referrals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"affiliateId","required":false,"in":"query","schema":{"type":"string"},"example":"AFF-4821"},{"name":"affiliateCode","required":false,"in":"query","schema":{"type":"string"},"example":"GRACE10"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"pending"},{"name":"program","required":false,"in":"query","schema":{"type":"string"},"example":"partner-2026"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Referrals","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Referrals with filters and paging — the ledger of what affiliates have driven and what stage each is at.\n\n#### Signature\n\n```http\nGET /affiliate/referrals (affiliateId?: string, affiliateCode?: string, status?: string, program?: string, page?: integer, pageSize?: integer) -> Referrals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/referrals/{id}`"}},"/affiliate/referrals/{id}":{"get":{"operationId":"AffiliateController_getReferral","summary":"Get a referral","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"200":{"description":"The referral with context","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Fetches one referral with its program, affiliate and full transition history resolved inline — the read for investigating why a commission is what it is.\n\n#### Signature\n\n```http\nGET /affiliate/referrals/{id} (id: string) -> The referral with context\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/referrals/{id}/transition`"}},"/affiliate/referrals/{id}/transition":{"post":{"operationId":"AffiliateController_transitionReferral","summary":"Transition a referral","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The transitioned referral","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"The single endpoint for every admin action on a referral: `approve`, `reject`, `hold`, `reverse`, `restore`, `expire`, `recalculate`, `override-amount`.\n\nThis is the one to use. `approve` and `reject` also exist as separate legacy endpoints that delegate here, but only this form covers the full set — holding a suspicious referral, reversing an approved one after a refund, or recalculating after a rate correction.\n\n**Approving credits the affiliate's wallet.** Reversing an already-approved referral debits it back.\n\n#### Signature\n\n```http\nPOST /affiliate/referrals/{id}/transition (id: string, body) -> The transitioned referral\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Approval moves money into an affiliate wallet. Reverse rather than delete when something needs undoing.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/referrals/bulk-transition`","requestBody":{"description":"The action to take.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["approve","reject","hold","reverse","restore","expire","recalculate","override-amount"],"example":"approve"},"reason":{"type":"string","example":"Order confirmed and past the refund window"},"amount":{"type":"number","description":"Required for `override-amount`.","example":12.5}}},"examples":{"approve":{"summary":"Approve — credits the wallet","value":{"action":"approve","reason":"Order confirmed"}},"hold":{"summary":"Hold a suspicious referral","value":{"action":"hold","reason":"Possible self-referral"}},"reverse":{"summary":"Reverse after a refund","value":{"action":"reverse","reason":"Order refunded"}},"override":{"summary":"Override the commission amount","value":{"action":"override-amount","amount":12.5,"reason":"Negotiated rate applied late"}}}}}}}},"/affiliate/referrals/bulk-transition":{"post":{"operationId":"AffiliateController_bulkTransitionReferrals","summary":"Transition referrals in bulk","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Per-referral results","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Applies a transition to many referrals at once — by explicit ids, or by a **filter** on status, program, affiliate or date range.\n\nThe filter form is powerful and dangerous: a broad filter with `approve` credits every matching affiliate wallet in one call. Run the same filter through `GET /affiliate/referrals` first and check the count.\n\n#### Signature\n\n```http\nPOST /affiliate/referrals/bulk-transition (body) -> Per-referral results\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Verify the filter against the referral list before running an approve.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/referrals`","requestBody":{"description":"What to transition, and how.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"action":{"type":"string","enum":["approve","reject","hold","reverse","restore","expire","recalculate","override-amount"],"example":"approve"},"ids":{"type":"array","items":{"type":"string"},"description":"Explicit referral ids."},"status":{"type":"string","description":"Filter form — transition everything matching.","example":"pending"},"program":{"type":"string","example":"partner-2026"},"affiliateId":{"type":"string"},"from":{"type":"string","example":"2026-09-01"},"to":{"type":"string","example":"2026-09-30"},"reason":{"type":"string"}}},"examples":{"byIds":{"summary":"Approve specific referrals","value":{"action":"approve","ids":["REF-4821","REF-4822"]}},"byFilter":{"summary":"Approve a month for one program","description":"Check the matching count first.","value":{"action":"approve","status":"pending","program":"partner-2026","from":"2026-09-01","to":"2026-09-30"}}}}}}}},"/affiliate/referrals/audit/report":{"get":{"operationId":"AffiliateController_auditReferrals","summary":"Run a referral data-quality audit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The audit report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Surfaces referrals that are wrong in ways nobody notices: stuck in a stage far too long, carrying zero commission when they should not, or orphaned from their affiliate or program.\n\nWorth running before a commission payout — each of these is either an affiliate not being paid what they earned, or the reverse.\n\n#### Signature\n\n```http\nGET /affiliate/referrals/audit/report () -> The audit report\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/referrals/expire-stale`"}},"/affiliate/referrals/{id}/approve":{"post":{"operationId":"AffiliateController_approveReferral","summary":"Approve a referral (legacy)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The approved referral","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Approves a referral and credits the affiliate's wallet immediately. A legacy alias for `transition` with `approve` — prefer the transition endpoint, which supports the full action set.\n\n#### Signature\n\n```http\nPOST /affiliate/referrals/{id}/approve (id: string, body) -> The approved referral\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Credits a real wallet balance.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/referrals/{id}/transition`","requestBody":{"description":"Optional detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Order confirmed"}}}}}},"/affiliate/referrals/{id}/reject":{"post":{"operationId":"AffiliateController_rejectReferral","summary":"Reject a referral (legacy)","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"REC-4821"}],"responses":{"201":{"description":"The rejected referral","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Rejects a referral so no commission is paid. A legacy alias for `transition` with `reject`.\n\n#### Signature\n\n```http\nPOST /affiliate/referrals/{id}/reject (id: string, body) -> The rejected referral\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/referrals/{id}/transition`","requestBody":{"description":"Why it was rejected.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Self-referral"}}}}}},"/affiliate/stats":{"get":{"operationId":"AffiliateController_getStats","summary":"Get affiliate statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Overall affiliate figures — referral volume, conversion and commission owed.\n\n#### Signature\n\n```http\nGET /affiliate/stats () -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/stats/top-affiliates`"}},"/affiliate/stats/program/{programName}":{"get":{"operationId":"AffiliateController_getProgramStats","summary":"Get program statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"programName","required":true,"in":"path","schema":{"type":"string"},"description":"Program name.","example":"partner-2026"}],"responses":{"200":{"description":"Program statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Figures for one program — whether its commission structure is actually working.\n\n#### Signature\n\n```http\nGET /affiliate/stats/program/{programName} (programName: string) -> Program statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/stats`"}},"/affiliate/stats/affiliate/{affiliateId}":{"get":{"operationId":"AffiliateController_getAffiliateStats","summary":"Get affiliate statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"affiliateId","required":true,"in":"path","schema":{"type":"string"},"description":"Affiliate id.","example":"AFF-4821"}],"responses":{"200":{"description":"Affiliate statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Figures for one affiliate — what they have driven and earned.\n\n#### Signature\n\n```http\nGET /affiliate/stats/affiliate/{affiliateId} (affiliateId: string) -> Affiliate statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/stats/top-affiliates`"}},"/affiliate/stats/top-affiliates":{"get":{"operationId":"AffiliateController_getTopAffiliates","summary":"Get top affiliates","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"string"},"description":"Maximum rows."}],"responses":{"200":{"description":"Top affiliates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"The best-performing affiliates by referral value.\n\n#### Signature\n\n```http\nGET /affiliate/stats/top-affiliates (limit?: string) -> Top affiliates\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/stats`"}},"/affiliate/commissions/process-held":{"post":{"operationId":"AffiliateController_processHeldCommissions","summary":"Process held commissions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was processed","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Releases commissions that were held pending review, crediting the affiliate wallets. Money moves — confirm the holds were resolved rather than merely aged out.\n\n#### Signature\n\n```http\nPOST /affiliate/commissions/process-held (body) -> What was processed\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Credits real wallet balances across affiliates.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/referrals/{id}/transition`","requestBody":{"description":"Optional options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/affiliate/referrals/expire-stale":{"post":{"operationId":"AffiliateController_expireStaleReferrals","summary":"Expire stale referrals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was expired","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Expires referrals that have sat unconverted past their attribution window. Housekeeping — but it closes referrals permanently, so check the audit report first for anything stuck rather than genuinely stale.\n\n#### Signature\n\n```http\nPOST /affiliate/referrals/expire-stale (body) -> What was expired\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Distinguish \"stale\" from \"stuck\" before running it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /affiliate/referrals/audit/report`","requestBody":{"description":"Optional options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/affiliate/test/attribute-order":{"post":{"operationId":"AffiliateController_testAttributeOrder","summary":"Test order attribution","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The attribution result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate"],"description":"Runs the attribution logic against a test order and reports which affiliate would be credited and why — the way to verify attribution rules without waiting for a real order.\n\n#### Signature\n\n```http\nPOST /affiliate/test/attribute-order (body) -> The attribution result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Diagnostic — check whether it writes a referral before running it against production data.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/public/track`","requestBody":{"description":"The test order.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"code":"GRACE10","orderNumber":"A7K2M9QX4","amount":129.99}}}}}},"/client/affiliate/join":{"post":{"operationId":"AffiliateClientController_join","summary":"Join an affiliate program","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The affiliate record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Could not determine your email. Please ensure your profile is complete. — The caller has no email on their profile.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Could not determine your email. Please ensure your profile is complete.","path":"/client/affiliate/join","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Program not found — No affiliate program has that name.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Program not found","path":"/client/affiliate/join","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · My account"],"description":"Enrols the signed-in customer as an affiliate. Their email must be resolvable from their profile — that is what the affiliate record is keyed on.\n\n#### Signature\n\n```http\nPOST /client/affiliate/join (body) -> The affiliate record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PROGRAM_NOT_FOUND | Program not found | No affiliate program has that name. | List programs with `GET /affiliate/programs`. |\n| `400` | NO_EMAIL | Could not determine your email. Please ensure your profile is complete. | The caller has no email on their profile. | Complete the profile first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/affiliate/me`","requestBody":{"description":"Which program to join.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"program":"partner-2026"}}}}}},"/client/affiliate/me":{"get":{"operationId":"AffiliateClientController_getMyProfile","summary":"Get my affiliate account","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The affiliate account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"You are not enrolled in this affiliate program — The caller has no affiliate record.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"You are not enrolled in this affiliate program","path":"/client/affiliate/me","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · My account"],"description":"The caller's own affiliate record — their code, status and program.\n\n#### Signature\n\n```http\nGET /client/affiliate/me () -> The affiliate account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_ENROLLED | You are not enrolled in this affiliate program | The caller has no affiliate record. | Join with `POST /client/affiliate/join`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/affiliate/me/earnings`"}},"/client/affiliate/me/link":{"post":{"operationId":"AffiliateClientController_generateMyLink","summary":"Create my affiliate link","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"program","required":false,"in":"query","schema":{"type":"string"},"description":"Affiliate program name; omit for the caller's default program."}],"responses":{"201":{"description":"The tracking link","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · My account"],"description":"Generates a tracking link for the calling affiliate — what they share to earn commission.\n\n#### Signature\n\n```http\nPOST /client/affiliate/me/link (program?: string, body) -> The tracking link\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/affiliate/me/link`","requestBody":{"description":"Optional destination.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"productSlug":"cola-330ml"}}}}},"get":{"operationId":"AffiliateClientController_getMyLink","summary":"Get my affiliate link","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"program","required":false,"in":"query","schema":{"type":"string"},"description":"Affiliate program name; omit for the caller's default program."}],"responses":{"200":{"description":"The tracking link","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · My account"],"description":"The calling affiliate's default tracking link.\n\n#### Signature\n\n```http\nGET /client/affiliate/me/link (program?: string) -> The tracking link\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/affiliate/me/link`"}},"/client/affiliate/me/referrals":{"get":{"operationId":"AffiliateClientController_getMyReferrals","summary":"Get my referrals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"program","required":false,"in":"query","schema":{"type":"string"},"description":"Affiliate program name; omit for the caller's default program."},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"pending"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"string"},"description":"Rows per page."}],"responses":{"200":{"description":"Referrals","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · My account"],"description":"The calling affiliate's own referrals and their status — what they have driven and what is still pending approval.\n\n#### Signature\n\n```http\nGET /client/affiliate/me/referrals (program?: string, pageSize?: string, status?: string, page?: integer) -> Referrals\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/affiliate/me/earnings`"}},"/client/affiliate/me/earnings":{"get":{"operationId":"AffiliateClientController_getMyEarnings","summary":"Get my earnings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"program","required":false,"in":"query","schema":{"type":"string"},"description":"Affiliate program name; omit for the caller's default program."},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Page number (1-based)."},{"name":"pageSize","required":false,"in":"query","schema":{"type":"string"},"description":"Rows per page."}],"responses":{"200":{"description":"Earnings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · My account"],"description":"The calling affiliate's commission — earned, pending and paid. Pending amounts are not guaranteed: a referral can still be reversed if the underlying order is refunded.\n\n#### Signature\n\n```http\nGET /client/affiliate/me/earnings (program?: string, page?: string, pageSize?: string) -> Earnings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Pending commission can still be reversed.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/affiliate/me/referrals`"}},"/client/affiliate/programs":{"get":{"operationId":"AffiliateClientController_getAffiliatePrograms","summary":"List joinable programs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Programs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · My account"],"description":"Affiliate programs the caller can join, with their commission terms.\n\n#### Signature\n\n```http\nGET /client/affiliate/programs () -> Programs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/affiliate/join`"}},"/affiliate/public/resolve/{code}":{"get":{"operationId":"AffiliatePublicController_resolveCode","summary":"Resolve a referral code","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"code","required":true,"in":"path","description":"Referral code.","schema":{"type":"string"},"example":"GRACE10"}],"responses":{"200":{"description":"The resolved code","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · Public"],"description":"Resolves a referral code to its affiliate and destination — what a landing page calls to confirm a code before applying it. Public, so it confirms which codes exist; codes are not secrets, but rate-limit brute-force enumeration.\n\n#### Signature\n\n```http\nGET /affiliate/public/resolve/{code} (code: string) -> The resolved code\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/public/track`"}},"/affiliate/public/track":{"post":{"operationId":"AffiliatePublicController_trackClick","summary":"Track a referral click","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The tracking result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · Public"],"description":"Records a click on an affiliate link, starting the attribution window. **Public and unauthenticated**, which is inherent — the visitor has no account yet.\n\nThat also means anyone can post clicks. Rate-limit it and treat inflated click counts from a single affiliate as a signal worth auditing.\n\n#### Signature\n\n```http\nPOST /affiliate/public/track (body) -> The tracking result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — click counts are self-reported by whoever calls it.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/public/apply-code`","requestBody":{"description":"The click.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","example":"GRACE10"},"email":{"type":"string","example":"ada@example.com"},"source":{"type":"string","enum":["link","code","qr"],"example":"link"}}},"example":{"code":"GRACE10","source":"link"}}}}}},"/affiliate/public/apply-code":{"post":{"operationId":"AffiliatePublicController_applyCode","summary":"Apply a referral code at checkout","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Affiliate · Public"],"description":"Attaches a referral code to a checkout session, so a resulting order is attributed to the affiliate. Public — the shopper is applying it themselves, usually without an account.\n\n#### Signature\n\n```http\nPOST /affiliate/public/apply-code (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /affiliate/test/attribute-order`","requestBody":{"description":"The code to apply.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","example":"GRACE10"},"email":{"type":"string","example":"ada@example.com"}}},"example":{"code":"GRACE10","email":"ada@example.com"}}}}}},"/broadcast/unsubscribe":{"get":{"operationId":"BroadcastController_unsubscribe","parameters":[{"name":"orgid","required":true,"in":"query","schema":{"type":"string"}},{"name":"e","required":true,"in":"query","schema":{"type":"string"},"description":"Recipient email."},{"name":"b","required":false,"in":"query","schema":{"type":"string"},"description":"Broadcast id."},{"name":"s","required":true,"in":"query","schema":{"type":"string"},"description":"Signature over org, email and broadcast."},{"name":"format","required":false,"in":"query","schema":{"type":"string","enum":["json"]}}],"responses":{"200":{"description":"A redirect, an HTML page, or JSON with `format=json`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This unsubscribe link is not valid. — The signature does not match.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This unsubscribe link is not valid.","path":"/broadcast/unsubscribe","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Unsubscribe from a bulk email link","description":"The signed link every bulk email carries; never needs a login. The query carries the org, address, broadcast and signature (`orgid`, `e`, `b`, `s`). A valid link adds the address to the org's suppression list. `format=json` answers `{ success, email }` (or 400 `{ success: false, error }`); otherwise the visitor is redirected (302) to the org's own result page, or shown a plain confirmation page when the org has no site.\n\n#### Signature\n\n```http\nGET /broadcast/unsubscribe (orgid?: string, e?: string, b?: string, s?: string, format?: string) -> A redirect, an HTML page, or JSON with `format=json`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BAD_LINK | This unsubscribe link is not valid. | The signature does not match. | — |\n\nPlus the standard platform errors: `429`, `500`.","tags":["Broadcast"]}},"/broadcast/audience/count":{"post":{"operationId":"BroadcastController_countAudience","summary":"Audience size per list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ <listId>: count }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"description":"For each list or group id (up to 50), how many people a send would reach — tagged customers included, unsubscribed and bounced addresses left out.\n\n#### Signature\n\n```http\nPOST /broadcast/audience/count (body) -> { <listId>: count }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Broadcast"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lists":{"type":"array","items":{"type":"string"}}}},"example":{"lists":["newsletter","vip"]}}}}}},"/broadcast/suppression":{"get":{"operationId":"BroadcastController_getSuppression","summary":"Suppression list","description":"Addresses this org may not bulk-email: unsubscribed, complained or bounced. Newest change first, paged; `search` matches the address. Includes counts per reason.\n\n#### Signature\n\n```http\nGET /broadcast/suppression (status?: string, search?: string, page?: integer, pageSize?: integer) -> Paged subscribers with counts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"One suppression status."},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"description":"Max 200."}],"responses":{"200":{"description":"Paged subscribers with counts","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Broadcast"]}},"/broadcast/suppression/remove":{"post":{"operationId":"BroadcastController_removeSuppression","summary":"Remove an address from suppression","description":"Puts an address back in circulation. Do this only at the recipient's own request.\n\n#### Signature\n\n```http\nPOST /broadcast/suppression/remove (body) -> { success, email, restored }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_REQUIRED | email is required | No email. | — |\n| `404` | NOT_SUPPRESSED | That address is not on the suppression list | Nothing to restore. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ success, email, restored }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"email is required — No email.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"email is required","path":"/broadcast/suppression/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"That address is not on the suppression list — Nothing to restore.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"That address is not on the suppression list","path":"/broadcast/suppression/remove","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Broadcast"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}}},"example":{"email":"ada@example.com"}}}}}},"/broadcast/accounts/{id}/test":{"post":{"operationId":"BroadcastController_testAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Email account id.","example":"ACC-12"}],"responses":{"201":{"description":"The test result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Email provider not configured — The org has no email provider integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email provider not configured","path":"/broadcast/accounts/{id}/test","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Email account not found — No account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Email account not found","path":"/broadcast/accounts/{id}/test","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Test a sending account","description":"Sends a test message through a sending account to confirm it works end to end. The cheapest check before a real broadcast — it catches an unverified domain or a broken provider credential before thousands of recipients do.\n\n#### Signature\n\n```http\nPOST /broadcast/accounts/{id}/test (id: string, body) -> The test result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends a real email.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ACCOUNT_NOT_FOUND | Email account not found | No account has that id. | Check the id. |\n| `400` | PROVIDER_NOT_CONFIGURED | Email provider not configured | The org has no email provider integration. | Configure the provider first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/health`","tags":["Broadcast"],"requestBody":{"description":"Optional test recipient.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"to":"ops@example.com"}}}}}},"/broadcast/{id}/send":{"post":{"operationId":"BroadcastController_triggerSend","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Broadcast id.","example":"BC-4821"}],"responses":{"201":{"description":"The send result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot send broadcast with status \"<status>\" — The broadcast is already sending, sent or cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot send broadcast with status \"<status>\"","path":"/broadcast/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Broadcast not found — No broadcast has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Broadcast not found","path":"/broadcast/{id}/send","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Send a broadcast","description":"Releases a broadcast to its recipient list. **This sends real email to real people and cannot be undone** — `cancel` only stops what has not yet been handed to the provider.\n\nBefore calling: check the recipient count on the broadcast, confirm the sending domain is verified, and send yourself a test. A broadcast can only be sent from a status that allows it; one already sending or sent is refused rather than duplicated.\n\n#### Signature\n\n```http\nPOST /broadcast/{id}/send (id: string) -> The send result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Irreversible, outward-facing bulk send.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BROADCAST_NOT_FOUND | Broadcast not found | No broadcast has that id. | Check the id. |\n| `400` | CANNOT_SEND | Cannot send broadcast with status \"<status>\" | The broadcast is already sending, sent or cancelled. | Duplicate it and send the copy. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/{id}/cancel`\n- `POST /broadcast/accounts/{id}/test`","tags":["Broadcast"]}},"/broadcast/{id}/cancel":{"post":{"operationId":"BroadcastController_cancelBroadcast","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Broadcast id.","example":"BC-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot cancel broadcast with status \"<status>\" — The broadcast is already finished or was never started.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot cancel broadcast with status \"<status>\"","path":"/broadcast/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Broadcast not found — No broadcast has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Broadcast not found","path":"/broadcast/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Cancel a broadcast","description":"Stops a broadcast. Messages already handed to the provider **have been sent** — cancelling halts the remainder, it does not recall anything. Speed matters here.\n\n#### Signature\n\n```http\nPOST /broadcast/{id}/cancel (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Does not recall messages already sent.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BROADCAST_NOT_FOUND | Broadcast not found | No broadcast has that id. | Check the id. |\n| `400` | CANNOT_CANCEL | Cannot cancel broadcast with status \"<status>\" | The broadcast is already finished or was never started. | Nothing left to stop. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/{id}/send`","tags":["Broadcast"]}},"/broadcast/{id}/duplicate":{"post":{"operationId":"BroadcastController_duplicateBroadcast","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Broadcast id.","example":"BC-4821"}],"responses":{"201":{"description":"The new draft","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Broadcast not found — No broadcast has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Broadcast not found","path":"/broadcast/{id}/duplicate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Duplicate a broadcast","description":"Copies a broadcast into a new draft — the way to re-send a campaign, since a sent broadcast cannot be sent again. The copy starts unsent, with no recipients marked as delivered.\n\n#### Signature\n\n```http\nPOST /broadcast/{id}/duplicate (id: string) -> The new draft\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BROADCAST_NOT_FOUND | Broadcast not found | No broadcast has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/{id}/send`","tags":["Broadcast"]}},"/broadcast/{id}/report":{"get":{"operationId":"BroadcastController_getBroadcastReport","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Broadcast id.","example":"BC-4821"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"e.g. `delivered`, `bounced`, `complained`.","example":"bounced"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based)."},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"description":"Rows per page."},{"name":"runId","required":false,"in":"query","schema":{"type":"string"},"description":"One run of the broadcast; omit for the latest."}],"responses":{"200":{"description":"Per-recipient outcomes","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Broadcast not found — No broadcast has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Broadcast not found","path":"/broadcast/{id}/report","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a broadcast report","description":"Per-recipient outcomes for a broadcast, filterable by status — who was delivered to, who bounced, who complained. Bounces and complaints are the ones worth acting on: leaving them on the list degrades the sending domain's reputation.\n\n#### Signature\n\n```http\nGET /broadcast/{id}/report (id: string, page?: integer, pageSize?: integer, runId?: string, status?: string) -> Per-recipient outcomes\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BROADCAST_NOT_FOUND | Broadcast not found | No broadcast has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/{id}/stats`","tags":["Broadcast"]}},"/broadcast/{id}/stats":{"get":{"operationId":"BroadcastController_getBroadcastStats","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Broadcast id.","example":"BC-4821"},{"name":"runId","required":false,"in":"query","schema":{"type":"string"},"description":"One run of the broadcast; omit for the latest."}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Broadcast not found — No broadcast has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Broadcast not found","path":"/broadcast/{id}/stats","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get broadcast statistics","description":"Aggregate figures for one broadcast — sent, delivered, opened, clicked, bounced, complained.\n\n#### Signature\n\n```http\nGET /broadcast/{id}/stats (id: string, runId?: string) -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | BROADCAST_NOT_FOUND | Broadcast not found | No broadcast has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/{id}/report`","tags":["Broadcast"]}},"/broadcast/domains/register":{"post":{"operationId":"BroadcastController_registerDomain","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The registration, with the DNS records to publish","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Email provider not configured — The org has no email provider integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email provider not configured","path":"/broadcast/domains/register","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Register a sending domain","description":"Registers a domain with the email provider so mail can be sent from it. Registration is only the first step — the DNS records the provider returns must be published before the domain verifies and mail actually delivers.\n\n#### Signature\n\n```http\nPOST /broadcast/domains/register (body) -> The registration, with the DNS records to publish\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PROVIDER_NOT_CONFIGURED | Email provider not configured | The org has no email provider integration. | Configure the provider first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/domains/auto-configure`","tags":["Broadcast"],"requestBody":{"description":"The domain.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"example.com"}}},"example":{"domain":"example.com"}}}}}},"/broadcast/domains/register-email":{"post":{"operationId":"BroadcastController_registerEmail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The registration","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Email provider not configured — The org has no email provider integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Email provider not configured","path":"/broadcast/domains/register-email","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Register a sending address","description":"Registers a single address rather than a whole domain. **Sends a verification email to that address** — the owner has to click it, so use an address someone actually reads.\n\n#### Signature\n\n```http\nPOST /broadcast/domains/register-email (body) -> The registration\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sends a verification email.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PROVIDER_NOT_CONFIGURED | Email provider not configured | The org has no email provider integration. | Configure the provider first. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/domains/email-status/{email}`","tags":["Broadcast"],"requestBody":{"description":"The address.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","example":"news@example.com"}}},"example":{"email":"news@example.com"}}}}}},"/broadcast/domains/register/{domain}":{"delete":{"operationId":"BroadcastController_unregisterDomain","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain or address.","example":"example.com"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Unregister a domain","description":"Removes a domain or address from the email provider. Anything configured to send from it stops delivering — check what uses it before removing.\n\n#### Signature\n\n```http\nDELETE /broadcast/domains/register/{domain} (domain: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Breaks any sender still using it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/domains/register`","tags":["Broadcast"]}},"/broadcast/domains/status/{domain}":{"get":{"operationId":"BroadcastController_checkDomainStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain.","example":"example.com"}],"responses":{"200":{"description":"Registration and DNS status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Check a domain's status","description":"Whether a domain is registered, what DNS records it needs, and whether they are live — the diagnostic when mail is not delivering. Live verification, not a cached flag.\n\n#### Signature\n\n```http\nGET /broadcast/domains/status/{domain} (domain: string) -> Registration and DNS status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/domains/auto-configure`","tags":["Broadcast"]}},"/broadcast/domains/email-status/{email}":{"get":{"operationId":"BroadcastController_checkEmailStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"news@example.com"}],"responses":{"200":{"description":"Verification status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Check an address's verification","description":"Whether a registered sending address has been verified by its owner.\n\n#### Signature\n\n```http\nGET /broadcast/domains/email-status/{email} (email: string) -> Verification status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/domains/register-email`","tags":["Broadcast"]}},"/broadcast/domains/auto-configure":{"post":{"operationId":"BroadcastController_autoConfigureDNS","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Auto-configure sending DNS","description":"Publishes the provider's required DNS records automatically. **Only works for domains registered through this platform** — a domain hosted elsewhere has to have its records added at its own DNS provider.\n\nIt writes live DNS records, so it changes real resolution for the domain.\n\n#### Signature\n\n```http\nPOST /broadcast/domains/auto-configure (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Writes live DNS records.\n- Only for domains registered through the platform.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/domains/status/{domain}`","tags":["Broadcast"],"requestBody":{"description":"The domain.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"example.com"}}},"example":{"domain":"example.com"}}}}}},"/broadcast/validate/domain/{domain}":{"get":{"operationId":"BroadcastController_validateDomain","parameters":[{"name":"domain","required":true,"in":"path","schema":{"type":"string"},"description":"Domain.","example":"example.com"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Validate a domain","description":"Checks whether a domain is well-formed and plausibly usable for sending, before registering it.\n\n#### Signature\n\n```http\nGET /broadcast/validate/domain/{domain} (domain: string) -> Validation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/domains/register`","tags":["Broadcast"]}},"/broadcast/validate/email/{email}":{"get":{"operationId":"BroadcastController_validateEmail","parameters":[{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Email address.","example":"ada@example.com"},{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Validate an email address","description":"Checks an address for form and deliverability signals. Worth running over an imported list — sending to invalid addresses produces bounces, and a high bounce rate is what damages sender reputation.\n\n#### Signature\n\n```http\nGET /broadcast/validate/email/{email} (email: string) -> Validation result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/health`","tags":["Broadcast"]}},"/broadcast/health":{"get":{"operationId":"BroadcastController_healthDashboard","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Sending health","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get sending health","description":"Deliverability health across sending accounts — bounce and complaint rates, provider standing. Read this before a large send: a degraded account will not improve by sending more through it.\n\n#### Signature\n\n```http\nGET /broadcast/health () -> Sending health\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /broadcast/health/{accountId}/check`","tags":["Broadcast"]}},"/broadcast/health/{accountId}/check":{"post":{"operationId":"BroadcastController_triggerHealthCheck","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Email account id.","example":"ACC-12"}],"responses":{"201":{"description":"The health result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Email account not found — No account has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Email account not found","path":"/broadcast/health/{accountId}/check","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Run a health check on an account","description":"Forces a fresh health evaluation of one sending account rather than reading the last computed figures.\n\n#### Signature\n\n```http\nPOST /broadcast/health/{accountId}/check (accountId: string) -> The health result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ACCOUNT_NOT_FOUND | Email account not found | No account has that id. | Check the id. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/health`","tags":["Broadcast"]}},"/broadcast/activity":{"get":{"operationId":"BroadcastController_getEmailActivity","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"event","required":false,"in":"query","schema":{"type":"string"},"description":"e.g. `bounce`, `open`, `click`.","example":"bounce"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Maximum rows."},{"name":"recipient","required":false,"in":"query","schema":{"type":"string"},"description":"Only this recipient address."}],"responses":{"200":{"description":"Activity events","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get email activity","description":"The event stream for sent mail — deliveries, opens, clicks, bounces, complaints — filterable by date and event type.\n\n#### Signature\n\n```http\nGET /broadcast/activity (limit?: integer, recipient?: string, startDate?: string, endDate?: string, event?: string) -> Activity events\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/activity/{messageId}`","tags":["Broadcast"]}},"/broadcast/activity/{messageId}":{"get":{"operationId":"BroadcastController_getEmailDetail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"messageId","required":true,"in":"path","schema":{"type":"string"},"description":"Message id.","example":"MSG-4821"}],"responses":{"200":{"description":"The message activity","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get one message's activity","description":"The full event history for a single message — the trace to run when a recipient says they never received something.\n\n#### Signature\n\n```http\nGET /broadcast/activity/{messageId} (messageId: string) -> The message activity\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/sent/{messageId}`","tags":["Broadcast"]}},"/broadcast/channel-stats":{"get":{"operationId":"BroadcastController_getChannelStats","summary":"Per-channel stats","description":"Per channel, counted from the delivery log: sent today, sent this month, delivery rate and open rate.\n\n#### Signature\n\n```http\nGET /broadcast/channel-stats () -> { <channel>: { sentToday, sentThisMonth, deliveryRate, avgResponseRate } }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ <channel>: { sentToday, sentThisMonth, deliveryRate, avgResponseRate } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Broadcast"]}},"/broadcast/stats":{"get":{"operationId":"BroadcastController_getEmailStats","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"aggregatedBy","required":false,"in":"query","schema":{"type":"string"},"description":"Bucket size, e.g. `day`, `week`, `month`."}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get email statistics","description":"Send and engagement figures over a date range.\n\n#### Signature\n\n```http\nGET /broadcast/stats (aggregatedBy?: string, startDate?: string, endDate?: string) -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/stats/summary`","tags":["Broadcast"]}},"/broadcast/stats/summary":{"get":{"operationId":"BroadcastController_getStatsSummary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"days","required":false,"in":"query","schema":{"type":"integer"},"description":"Look-back window in days.","example":30}],"responses":{"200":{"description":"The summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a statistics summary","description":"Headline figures over the last N days — the compact version for a dashboard tile.\n\n#### Signature\n\n```http\nGET /broadcast/stats/summary (days?: integer) -> The summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/stats`","tags":["Broadcast"]}},"/broadcast/sent":{"get":{"operationId":"BroadcastController_getSentEmails","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-01"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-08-31"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"recipient","required":false,"in":"query","schema":{"type":"string"},"description":"Only this recipient address."}],"responses":{"200":{"description":"Sent messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List sent messages","description":"Messages the org has sent, newest first.\n\n#### Signature\n\n```http\nGET /broadcast/sent (recipient?: string, startDate?: string, endDate?: string, limit?: integer) -> Sent messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/sent/{messageId}`","tags":["Broadcast"]}},"/broadcast/sent/{messageId}":{"get":{"operationId":"BroadcastController_getSentEmailDetail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"messageId","required":true,"in":"path","schema":{"type":"string"},"description":"Message id.","example":"MSG-4821"}],"responses":{"200":{"description":"The message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a sent message","description":"One sent message with its content and delivery outcome — what the recipient was actually sent, which is the record to check in a dispute.\n\n#### Signature\n\n```http\nGET /broadcast/sent/{messageId} (messageId: string) -> The message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /broadcast/activity/{messageId}`","tags":["Broadcast"]}},"/client/broadcast/unsubscribe":{"post":{"operationId":"BroadcastClientController_unsubscribe","summary":"Unsubscribe an address (unsubscribe page)","description":"Public. Stops bulk email to the address typed on the org's unsubscribe page. `review: true` flags it for a compliance review; `broadcast` and `reason` are recorded with it.\n\n#### Signature\n\n```http\nPOST /client/broadcast/unsubscribe (body) -> { success: true, email, review }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EMAIL_REQUIRED | A valid email address is required. | `email` has no @. | — |\n\nPlus the standard platform errors: `429`, `500`.","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ success: true, email, review }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A valid email address is required. — `email` has no @.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A valid email address is required.","path":"/client/broadcast/unsubscribe","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Broadcast Client"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"review":{"type":"boolean"},"broadcast":{"type":"string"},"reason":{"type":"string"}}},"example":{"email":"ada@example.com","reason":"Too many emails"}}}}}},"/community/connections/request":{"post":{"operationId":"CommunityController_sendConnectionRequest","summary":"Send a connection request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The pending request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot connect with yourself — `targetId` is the caller.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot connect with yourself","path":"/community/connections/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Asks another member to connect. The request is pending until they respond — connecting is mutual, so nothing is visible to either side as a connection until it is accepted.\n\nSelf-requests, duplicates and requests to an already-connected member are all refused rather than silently ignored.\n\n#### Signature\n\n```http\nPOST /community/connections/request (body) -> The pending request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `400` | SELF_CONNECT | Cannot connect with yourself | `targetId` is the caller. | Pick another member. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /community/connections/{id}/respond`","requestBody":{"description":"Who to connect with.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["targetId"],"properties":{"targetId":{"type":"string","example":"MEM-7712"},"message":{"type":"string","example":"We met at the conference last week."},"context":{"type":"object","description":"Where the request originated — an event, a group.","additionalProperties":true}}},"example":{"targetId":"MEM-7712","message":"We met at the conference last week."}}}}}},"/community/connections/{id}/respond":{"put":{"operationId":"CommunityController_respondToConnection","summary":"Respond to a connection request","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Connection request id.","example":"CON-4821"}],"responses":{"200":{"description":"The updated connection","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to respond to this request — The caller is not the recipient.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to respond to this request","path":"/community/connections/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Connection request not found — No request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Connection request not found","path":"/community/connections/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Accepts or rejects a request. Only the **recipient** may respond — the sender gets a 403.\n\n#### Signature\n\n```http\nPUT /community/connections/{id}/respond (id: string, body) -> The updated connection\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | REQUEST_NOT_FOUND | Connection request not found | No request has that id. | Read the pending list. |\n| `403` | NOT_RECIPIENT | Not authorized to respond to this request | The caller is not the recipient. | Only the recipient can accept or reject. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/pending`","requestBody":{"description":"The response.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["accept","reject"],"example":"accept"}}},"example":{"action":"accept"}}}}}},"/community/connections":{"get":{"operationId":"CommunityController_getConnections","summary":"List connections","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"accepted"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"example":0}],"responses":{"200":{"description":"Connections","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"The caller's connections, optionally filtered by status.\n\n#### Signature\n\n```http\nGET /community/connections (status?: string, limit?: integer, offset?: integer) -> Connections\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/stats`"}},"/community/connections/pending":{"get":{"operationId":"CommunityController_getPendingRequests","summary":"List pending requests received","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Pending requests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections/pending","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Requests waiting on the caller's response — their inbox.\n\n#### Signature\n\n```http\nGET /community/connections/pending () -> Pending requests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/sent`"}},"/community/connections/sent":{"get":{"operationId":"CommunityController_getSentRequests","summary":"List pending requests sent","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Sent requests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections/sent","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Requests the caller has sent that are still unanswered.\n\n#### Signature\n\n```http\nGET /community/connections/sent () -> Sent requests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/pending`"}},"/community/connections/stats":{"get":{"operationId":"CommunityController_getConnectionStats","summary":"Get connection statistics","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections/stats","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Counts of connections, pending requests in and out.\n\n#### Signature\n\n```http\nGET /community/connections/stats () -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections`"}},"/community/connections/accept-all":{"post":{"operationId":"CommunityController_acceptAllRequests","summary":"Accept all pending requests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was accepted","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections/accept-all","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Accepts every request currently waiting on the caller in one call. Convenient, but indiscriminate — it connects the caller to everyone in the queue, including anyone they would have rejected. Review the pending list first.\n\n#### Signature\n\n```http\nPOST /community/connections/accept-all () -> What was accepted\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Accepts everything pending, without review.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/pending`"}},"/community/connections/{id}":{"delete":{"operationId":"CommunityController_removeConnection","summary":"Remove a connection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Connection id.","example":"CON-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/connections/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized — The caller is not part of the connection.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized","path":"/community/connections/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Connection not found — No connection has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Connection not found","path":"/community/connections/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Disconnects from a member, or withdraws a request the caller sent. The connection disappears for both sides.\n\n#### Signature\n\n```http\nDELETE /community/connections/{id} (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | CONNECTION_NOT_FOUND | Connection not found | No connection has that id. | Check the id. |\n| `403` | NOT_AUTHORIZED | Not authorized | The caller is not part of the connection. | Only participants can remove it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /community/blocks`"}},"/community/messages":{"post":{"operationId":"CommunityController_sendMessage","summary":"Send a direct message","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The sent message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Cannot send message to this user — A block exists in either direction, or the recipient restricts messages.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Cannot send message to this user","path":"/community/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Sends a message from the caller to another member. Blocks are enforced here — if either side has blocked the other the send is refused with 403, which is how blocking actually stops contact.\n\n#### Signature\n\n```http\nPOST /community/messages (body) -> The sent message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `403` | CANNOT_MESSAGE | Cannot send message to this user | A block exists in either direction, or the recipient restricts messages. | Nothing to retry. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/thread/{userId}`","requestBody":{"description":"The message.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["recipientId","content"],"properties":{"recipientId":{"type":"string","example":"MEM-7712"},"content":{"type":"string","example":"Are you free Thursday?"},"contentType":{"type":"string","description":"Defaults to plain text.","example":"text"},"attachments":{"type":"array","items":{"type":"object","additionalProperties":true}},"replyTo":{"type":"string","description":"Message being replied to.","example":"MSG-4820"},"context":{"type":"object","additionalProperties":true}}},"example":{"recipientId":"MEM-7712","content":"Are you free Thursday?"}}}}}},"/community/messages/threads":{"get":{"operationId":"CommunityController_getThreads","summary":"List conversation threads","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"example":0}],"responses":{"200":{"description":"Threads","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/messages/threads","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"The caller's conversations with their most recent message — the messaging inbox.\n\n#### Signature\n\n```http\nGET /community/messages/threads (limit?: integer, offset?: integer) -> Threads\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/thread/{userId}`"}},"/community/messages/thread/{userId}":{"get":{"operationId":"CommunityController_getThread","summary":"Get a conversation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The other member.","example":"MEM-7712"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"before","required":false,"in":"query","schema":{"type":"string"},"description":"Return messages older than this timestamp.","example":"2026-08-29T10:00:00.000Z"}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/messages/thread/{userId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Messages exchanged with one member, newest first. `before` pages backwards through history — pass the oldest message's timestamp from the previous page.\n\n#### Signature\n\n```http\nGET /community/messages/thread/{userId} (userId: string, limit?: integer, before?: string) -> Messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/messages/thread/{userId}/read`"}},"/community/messages/read":{"post":{"operationId":"CommunityController_markAsRead","summary":"Mark messages read","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/messages/read","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Marks specific messages as read by id. Use the thread form to clear a whole conversation.\n\n#### Signature\n\n```http\nPOST /community/messages/read (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/messages/thread/{userId}/read`","requestBody":{"description":"The messages.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["messageIds"],"properties":{"messageIds":{"type":"array","items":{"type":"string"},"example":["MSG-4820","MSG-4821"]}}},"example":{"messageIds":["MSG-4820","MSG-4821"]}}}}}},"/community/messages/thread/{userId}/read":{"post":{"operationId":"CommunityController_markThreadAsRead","summary":"Mark a conversation read","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The other member.","example":"MEM-7712"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/messages/thread/{userId}/read","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Clears the unread state for an entire conversation — what a client calls when the thread is opened.\n\n#### Signature\n\n```http\nPOST /community/messages/thread/{userId}/read (userId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/unread-count`"}},"/community/messages/{id}":{"delete":{"operationId":"CommunityController_deleteMessage","summary":"Delete a message","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Message id.","example":"MSG-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/messages/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized — The message is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized","path":"/community/messages/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Message not found — No message has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Message not found","path":"/community/messages/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Deletes one of the caller's own messages. Someone else's message cannot be deleted — that returns 403.\n\n#### Signature\n\n```http\nDELETE /community/messages/{id} (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MESSAGE_NOT_FOUND | Message not found | No message has that id. | Check the id. |\n| `403` | NOT_AUTHORIZED | Not authorized | The message is not the caller's. | Only the sender can delete. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /community/reports`"}},"/community/messages/unread-count":{"get":{"operationId":"CommunityController_getUnreadCount","summary":"Get the unread message count","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The count","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"count":3}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/messages/unread-count","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"How many unread messages the caller has — the badge count.\n\n#### Signature\n\n```http\nGET /community/messages/unread-count () -> The count\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/threads`"}},"/community/meetings":{"post":{"operationId":"CommunityController_createMeeting","summary":"Create a meeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"At least one participant is required — `participantIds` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"At least one participant is required","path":"/community/meetings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/meetings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Schedules a meeting between the caller and one or more participants. The caller becomes the organiser, and only the organiser can later edit it. Give either `endTime` or `duration`; `timezone` matters when participants are in different ones.\n\n#### Signature\n\n```http\nPOST /community/meetings (body) -> The meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `400` | PARTICIPANT_REQUIRED | At least one participant is required | `participantIds` is empty. | Invite someone. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}/respond`","requestBody":{"description":"The meeting.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["participantIds","startTime"],"properties":{"title":{"type":"string","example":"Intro call"},"participantIds":{"type":"array","items":{"type":"string"},"example":["MEM-7712"]},"startTime":{"type":"string","format":"date-time","example":"2026-09-03T14:00:00.000Z"},"endTime":{"type":"string","format":"date-time","example":"2026-09-03T14:30:00.000Z"},"duration":{"type":"integer","description":"Minutes. Alternative to `endTime`.","example":30},"timezone":{"type":"string","example":"America/New_York"},"locationType":{"type":"string","example":"virtual"},"location":{"type":"string","example":"Room 3"},"meetingLink":{"type":"string","example":"https://meet.example.com/abc"},"description":{"type":"string"},"event":{"type":"string","description":"Event this meeting belongs to."},"eventName":{"type":"string"}}},"example":{"title":"Intro call","participantIds":["MEM-7712"],"startTime":"2026-09-03T14:00:00.000Z","duration":30,"locationType":"virtual","meetingLink":"https://meet.example.com/abc"}}}}},"get":{"operationId":"CommunityController_getMeetings","summary":"List meetings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"accepted"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-30"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"Meetings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/meetings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Meetings the caller organises or attends, filterable by status and date range.\n\n#### Signature\n\n```http\nGET /community/meetings (status?: string, fromDate?: string, toDate?: string, limit?: integer) -> Meetings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/meetings/upcoming`"}},"/community/meetings/upcoming":{"get":{"operationId":"CommunityController_getUpcomingMeetings","summary":"List upcoming meetings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":5}],"responses":{"200":{"description":"Upcoming meetings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/meetings/upcoming","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"The caller's next meetings — what a home screen shows.\n\n#### Signature\n\n```http\nGET /community/meetings/upcoming (limit?: integer) -> Upcoming meetings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/meetings`"}},"/community/meetings/{id}":{"get":{"operationId":"CommunityController_getMeeting","summary":"Get a meeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/meetings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to view this meeting — The caller is neither organiser nor participant.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to view this meeting","path":"/community/meetings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/community/meetings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"One meeting with its participants and their responses. Only participants may read it.\n\n#### Signature\n\n```http\nGET /community/meetings/{id} (id: string) -> The meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | NOT_PARTICIPANT_VIEW | Not authorized to view this meeting | The caller is neither organiser nor participant. | Ask the organiser to invite you. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}/respond`"},"put":{"operationId":"CommunityController_updateMeeting","summary":"Update a meeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The updated meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/meetings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Only organizer can update the meeting — The caller is a participant, not the organiser.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only organizer can update the meeting","path":"/community/meetings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/community/meetings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Changes a meeting's details. **Organiser only** — participants get a 403. Moving the time re-opens the RSVPs, so participants who had accepted need to respond again.\n\n#### Signature\n\n```http\nPUT /community/meetings/{id} (id: string, body) -> The updated meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | ORGANIZER_ONLY | Only organizer can update the meeting | The caller is a participant, not the organiser. | Ask the organiser to make the change. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /community/meetings/{id}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"location":{"type":"string"},"meetingLink":{"type":"string"},"description":{"type":"string"}}},"example":{"startTime":"2026-09-03T15:00:00.000Z"}}}}},"delete":{"operationId":"CommunityController_cancelMeeting","summary":"Cancel a meeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/meetings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to cancel this meeting — The caller is not the organiser.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to cancel this meeting","path":"/community/meetings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/community/meetings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Cancels a meeting and notifies its participants. Organiser only. A `reason` is passed on to the participants, so it is worth writing one.\n\n#### Signature\n\n```http\nDELETE /community/meetings/{id} (id: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Notifies participants.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | NOT_AUTHORIZED_CANCEL | Not authorized to cancel this meeting | The caller is not the organiser. | Only the organiser can cancel. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}`","requestBody":{"description":"Optional reason, shown to participants.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Rescheduling to next week"}}},"example":{"reason":"Rescheduling to next week"}}}}}},"/community/meetings/{id}/respond":{"put":{"operationId":"CommunityController_respondToMeeting","summary":"Respond to a meeting invitation","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The updated meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/meetings/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not a participant of this meeting — The caller was not invited.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not a participant of this meeting","path":"/community/meetings/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/community/meetings/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Records the caller's RSVP. Only an invited participant can respond.\n\n#### Signature\n\n```http\nPUT /community/meetings/{id}/respond (id: string, body) -> The updated meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | NOT_PARTICIPANT | Not a participant of this meeting | The caller was not invited. | Only invitees can respond. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}`","requestBody":{"description":"The response.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["response"],"properties":{"response":{"type":"string","enum":["accepted","declined","tentative"],"example":"accepted"}}},"example":{"response":"accepted"}}}}}},"/community/blocks":{"post":{"operationId":"CommunityController_blockUser","summary":"Block a member","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The block","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/blocks","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Blocks another member. The block is enforced at the messaging layer in **both** directions — neither side can message the other afterwards, regardless of who blocked whom.\n\nSetting `report: true` files a moderation report at the same time, which is the right choice when the behaviour warrants review rather than only personal avoidance.\n\n#### Signature\n\n```http\nPOST /community/blocks (body) -> The block\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /community/blocks/{userId}`\n- `POST /community/reports`","requestBody":{"description":"Who to block.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["blockedId"],"properties":{"blockedId":{"type":"string","example":"MEM-7712"},"reason":{"type":"string","example":"harassment"},"reasonDetails":{"type":"string"},"report":{"type":"boolean","description":"Also file a moderation report.","example":true},"reportDetails":{"type":"string"},"context":{"type":"object","additionalProperties":true}}},"example":{"blockedId":"MEM-7712","reason":"harassment","report":true,"reportDetails":"Repeated unsolicited messages after being asked to stop."}}}}},"get":{"operationId":"CommunityController_getBlockedUsers","summary":"List blocked members","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Blocked members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/blocks","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Who the caller has blocked. Does not show who has blocked the caller — that is deliberately not disclosed.\n\n#### Signature\n\n```http\nGET /community/blocks () -> Blocked members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/blocks`"}},"/community/blocks/{userId}":{"delete":{"operationId":"CommunityController_unblockUser","summary":"Unblock a member","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The blocked member.","example":"MEM-7712"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/blocks/{userId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Block not found — That member is not blocked.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Block not found","path":"/community/blocks/{userId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Lifts a block, allowing contact again. Any report filed alongside the block stands — unblocking does not withdraw it.\n\n#### Signature\n\n```http\nDELETE /community/blocks/{userId} (userId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | BLOCK_NOT_FOUND | Block not found | That member is not blocked. | Read the block list. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/blocks`"}},"/community/reports":{"post":{"operationId":"CommunityController_reportUser","summary":"Report a member","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/community/reports","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"description":"Files a moderation report against another member. `details` is required and is what a moderator acts on — a report with no substance cannot be assessed.\n\nReporting does not block: to also stop contact, use `POST /community/blocks` with `report: true`, or block separately.\n\n#### Signature\n\n```http\nPOST /community/reports (body) -> The report\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Filing a report does not block the member.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/blocks`","requestBody":{"description":"The report.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reportedId","reason","details"],"properties":{"reportedId":{"type":"string","example":"MEM-7712"},"reason":{"type":"string","example":"harassment"},"details":{"type":"string","example":"Sent repeated unsolicited messages after being asked to stop."},"context":{"type":"object","description":"Where it happened — a post, a thread.","additionalProperties":true}}},"example":{"reportedId":"MEM-7712","reason":"harassment","details":"Sent repeated unsolicited messages after being asked to stop."}}}}},"get":{"operationId":"CommunityController_getPendingReports","summary":"Reports waiting for review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Community"]}},"/community/reports/{id}/review":{"post":{"operationId":"CommunityController_reviewReport","summary":"Review a report: action_taken or dismissed","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Community"]}},"/client/community/pages":{"get":{"operationId":"CommunitySocialClientController_getPages","summary":"List community pages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"group"},{"name":"category","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by category."},{"name":"q","required":false,"in":"query","schema":{"type":"string"},"description":"Search text."},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Maximum rows."},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based)."}],"responses":{"200":{"description":"Pages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"The community pages (spaces) in the org, optionally filtered by type. Public — a page list is discoverable by anyone.\n\n#### Signature\n\n```http\nGET /client/community/pages (category?: string, q?: string, limit?: integer, page?: integer, type?: string) -> Pages\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/pages/{pageId}`"},"post":{"operationId":"CommunitySocialClientController_createPage","summary":"Create a community page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The page","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/pages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Creates a page (a group or space) with the caller as its owner.\n\n#### Signature\n\n```http\nPOST /client/community/pages (body) -> The page\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/community/pages/{pageId}`","requestBody":{"description":"The page.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Photography","type":"group","description":"Share your shots."}}}}}},"/client/community/pages/mine":{"get":{"operationId":"CommunitySocialClientController_getMyPages","summary":"List my pages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based)."}],"responses":{"200":{"description":"Pages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/pages/mine","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Pages the caller owns or belongs to.\n\n#### Signature\n\n```http\nGET /client/community/pages/mine (page?: integer, limit?: integer) -> Pages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/pages/{pageId}/join`"}},"/client/community/pages/{pageId}":{"get":{"operationId":"CommunitySocialClientController_getPage","summary":"Get a community page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"}],"responses":{"200":{"description":"The page","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"One page with its description and settings.\n\n#### Signature\n\n```http\nGET /client/community/pages/{pageId} (pageId: string) -> The page\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/pages/{pageId}/join`"},"put":{"operationId":"CommunitySocialClientController_updatePage","summary":"Update a community page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"}],"responses":{"200":{"description":"The updated page","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/pages/{pageId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized — The caller does not own or administer the page.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized","path":"/client/community/pages/{pageId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Changes a page's details. Restricted to the page owner or an admin of it.\n\n#### Signature\n\n```http\nPUT /client/community/pages/{pageId} (pageId: string, body) -> The updated page\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n| `403` | NOT_AUTHORIZED | Not authorized | The caller does not own or administer the page. | Ask a page admin. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/pages/mine`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"description":"Share your shots — beginners welcome."}}}}}},"/client/community/pages/{pageId}/join":{"post":{"operationId":"CommunitySocialClientController_joinPage","summary":"Join a page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"}],"responses":{"201":{"description":"The membership","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/pages/{pageId}/join","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}/join","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Adds the caller as a member. Membership determines what appears in their feed.\n\n#### Signature\n\n```http\nPOST /client/community/pages/{pageId}/join (pageId: string) -> The membership\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/pages/{pageId}/leave`"}},"/client/community/pages/{pageId}/leave":{"post":{"operationId":"CommunitySocialClientController_leavePage","summary":"Leave a page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/pages/{pageId}/leave","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}/leave","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Removes the caller's membership. Their existing posts on the page stay — leaving is not a deletion.\n\n#### Signature\n\n```http\nPOST /client/community/pages/{pageId}/leave (pageId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Posts already made remain visible.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/pages/{pageId}/join`"}},"/client/community/pages/{pageId}/invite":{"post":{"operationId":"CommunitySocialClientController_invitePage","summary":"Invite someone to a page","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"}],"responses":{"201":{"description":"The invitation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/pages/{pageId}/invite","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}/invite","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Invites a member to join a page. This reaches the invitee as a notification, so it is outward-facing.\n\n#### Signature\n\n```http\nPOST /client/community/pages/{pageId}/invite (pageId: string, body) -> The invitation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/pages/{pageId}/members`","requestBody":{"description":"Who to invite.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"email":"ada@example.com"}}}}}},"/client/community/pages/{pageId}/members":{"get":{"operationId":"CommunitySocialClientController_getPageMembers","summary":"List page members","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"},{"name":"role","required":false,"in":"query","schema":{"type":"string"},"description":"Only members with this role."},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}/members","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Who belongs to a page. **Public** — member lists are readable without authentication, which means membership of a page is not private.\n\n#### Signature\n\n```http\nGET /client/community/pages/{pageId}/members (pageId: string, role?: string, limit?: integer, page?: integer) -> Members\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n- Page membership is not private.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/people`"}},"/client/community/feed":{"get":{"operationId":"CommunitySocialClientController_getFeed","summary":"Get the community feed","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Community **page id** to filter by — not a pagination cursor.","example":"PG-4821"},{"name":"author","required":false,"in":"query","schema":{"type":"string"},"example":"ada@example.com"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Content type.","example":"poll"},{"name":"hashtag","required":false,"in":"query","schema":{"type":"string"},"example":"photography"},{"name":"sort","required":false,"in":"query","schema":{"type":"string"},"description":"Defaults to `latest`.","example":"latest"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"pageNum","required":false,"in":"query","schema":{"type":"integer"},"description":"The actual pagination parameter.","example":1}],"responses":{"200":{"description":"Feed posts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"The main feed, filterable by page, author, content type and hashtag. `sort` defaults to `latest`.\n\nPublic, but **viewer-aware**: when a token is present the caller's own reactions and visibility are reflected, and without one the feed is the anonymous view. Note it takes both `limit`/`pageNum` and a separate `page` parameter — `page` filters to a community page, it is not pagination.\n\n#### Signature\n\n```http\nGET /client/community/feed (page?: string, author?: string, type?: string, hashtag?: string, sort?: string, limit?: integer, pageNum?: integer) -> Feed posts\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n- `page` filters by community page; `pageNum` paginates.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/posts/{postId}`"}},"/client/community/posts":{"post":{"operationId":"CommunitySocialClientController_createPost","summary":"Create a post","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The post","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/posts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Publishes a post as the caller. Posts on a public page are readable without authentication, so treat anything posted as public unless the page is restricted.\n\n#### Signature\n\n```http\nPOST /client/community/posts (body) -> The post\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Content may be world-readable.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/community/posts/{postId}`","requestBody":{"description":"The post.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"page":"PG-4821","content":"First shot with the new lens.","contentType":"text","attachments":[]}}}}}},"/client/community/posts/{postId}":{"get":{"operationId":"CommunitySocialClientController_getPost","summary":"Get a post","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"postId","required":true,"in":"path","schema":{"type":"string"},"description":"Post id.","example":"PST-4821"}],"responses":{"200":{"description":"The post","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Post not found — No post has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Post not found","path":"/client/community/posts/{postId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"One post with its content and engagement counts.\n\n#### Signature\n\n```http\nGET /client/community/posts/{postId} (postId: string) -> The post\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POST_NOT_FOUND | Post not found | No post has that id. | Check the id from the feed. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/posts/{postId}/comments`"},"put":{"operationId":"CommunitySocialClientController_updatePost","summary":"Update a post","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"postId","required":true,"in":"path","schema":{"type":"string"},"description":"Post id.","example":"PST-4821"}],"responses":{"200":{"description":"The updated post","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/posts/{postId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your post — The post belongs to another member.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your post","path":"/client/community/posts/{postId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Post not found — No post has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Post not found","path":"/client/community/posts/{postId}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Edits the caller's own post. Someone else's post cannot be edited.\n\n#### Signature\n\n```http\nPUT /client/community/posts/{postId} (postId: string, body) -> The updated post\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | POST_NOT_FOUND | Post not found | No post has that id. | Check the id from the feed. |\n| `403` | NOT_YOUR_POST | Not your post | The post belongs to another member. | Only the author can edit or delete it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /client/community/posts/{postId}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"content":"First shot with the new lens (corrected)."}}}}},"delete":{"operationId":"CommunitySocialClientController_deletePost","summary":"Delete a post","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"postId","required":true,"in":"path","schema":{"type":"string"},"description":"Post id.","example":"PST-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/posts/{postId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your post — The post belongs to another member.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your post","path":"/client/community/posts/{postId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Post not found — No post has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Post not found","path":"/client/community/posts/{postId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Deletes the caller's own post, along with its comments and reactions.\n\n#### Signature\n\n```http\nDELETE /client/community/posts/{postId} (postId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Comments and reactions go with it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | POST_NOT_FOUND | Post not found | No post has that id. | Check the id from the feed. |\n| `403` | NOT_YOUR_POST | Not your post | The post belongs to another member. | Only the author can edit or delete it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/reports`"}},"/client/community/posts/{postId}/share":{"post":{"operationId":"CommunitySocialClientController_sharePost","summary":"Share a post","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"postId","required":true,"in":"path","schema":{"type":"string"},"description":"Post to share.","example":"PST-4821"}],"responses":{"201":{"description":"The share","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/posts/{postId}/share","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Post not found — No post has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Post not found","path":"/client/community/posts/{postId}/share","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Reposts someone's post to the caller's own feed, optionally with a comment. The original stays where it is — this creates a new post referencing it.\n\n#### Signature\n\n```http\nPOST /client/community/posts/{postId}/share (postId: string, body) -> The share\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | POST_NOT_FOUND | Post not found | No post has that id. | Check the id from the feed. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/posts`","requestBody":{"description":"Optional commentary.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"type":"string","example":"Worth a look."}}},"example":{"comment":"Worth a look."}}}}}},"/client/community/posts/{postId}/vote":{"post":{"operationId":"CommunitySocialClientController_votePoll","summary":"Vote in a poll","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"postId","required":true,"in":"path","schema":{"type":"string"},"description":"Poll post id.","example":"PST-4821"}],"responses":{"201":{"description":"The vote result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Not a poll — The post is not a poll.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Not a poll","path":"/client/community/posts/{postId}/vote","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/posts/{postId}/vote","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Post not found — No post has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Post not found","path":"/client/community/posts/{postId}/vote","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Casts the caller's vote on a poll post. One vote per member — a second attempt is refused rather than replacing the first, so votes cannot be changed.\n\n#### Signature\n\n```http\nPOST /client/community/posts/{postId}/vote (postId: string, body) -> The vote result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Votes are final.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | POST_NOT_FOUND | Post not found | No post has that id. | Check the id from the feed. |\n| `400` | NOT_A_POLL | Not a poll | The post is not a poll. | Check the post type. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/posts/{postId}`","requestBody":{"description":"The chosen option.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["optionId"],"properties":{"optionId":{"type":"string","example":"opt-2"}}},"example":{"optionId":"opt-2"}}}}}},"/client/community/posts/{postId}/comments":{"get":{"operationId":"CommunitySocialClientController_getComments","summary":"Get post comments","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"postId","required":true,"in":"path","schema":{"type":"string"},"description":"Post id.","example":"PST-4821"},{"name":"parentComment","required":false,"in":"query","schema":{"type":"string"},"description":"Fetch replies to this comment.","example":"CMT-991"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Comments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Comments on a post. Pass `parentComment` to fetch replies to a specific comment rather than the top level.\n\n#### Signature\n\n```http\nGET /client/community/posts/{postId}/comments (postId: string, parentComment?: string, limit?: integer, page?: integer) -> Comments\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/posts/{postId}/comments`"},"post":{"operationId":"CommunitySocialClientController_addComment","summary":"Add a comment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"postId","required":true,"in":"path","schema":{"type":"string"},"description":"Post id.","example":"PST-4821"}],"responses":{"201":{"description":"The comment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/posts/{postId}/comments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Post not found — No post has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Post not found","path":"/client/community/posts/{postId}/comments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Comments on a post as the caller. Set `parentComment` in the body to reply to another comment instead of the post.\n\n#### Signature\n\n```http\nPOST /client/community/posts/{postId}/comments (postId: string, body) -> The comment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | POST_NOT_FOUND | Post not found | No post has that id. | Check the id from the feed. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/community/comments/{commentId}`","requestBody":{"description":"The comment.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"content":"Great shot.","parentComment":null}}}}}},"/client/community/comments/{commentId}":{"delete":{"operationId":"CommunitySocialClientController_deleteComment","summary":"Delete a comment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-991"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/comments/{commentId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your comment — The comment belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your comment","path":"/client/community/comments/{commentId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Comment not found — No comment has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Comment not found","path":"/client/community/comments/{commentId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Deletes the caller's own comment. Someone else's comment cannot be deleted — report it instead.\n\n#### Signature\n\n```http\nDELETE /client/community/comments/{commentId} (commentId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | COMMENT_NOT_FOUND | Comment not found | No comment has that id. | Check the id. |\n| `403` | NOT_YOUR_COMMENT | Not your comment | The comment belongs to someone else. | Report it instead. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/reports`"}},"/client/community/react":{"post":{"operationId":"CommunitySocialClientController_react","summary":"React to something","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The reaction","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/react","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Adds a reaction to a post, comment, story or message. One endpoint for all four — `targetType` says which, `type` is the reaction itself.\n\n#### Signature\n\n```http\nPOST /client/community/react (body) -> The reaction\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/reactions`","requestBody":{"description":"The reaction.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["target","targetType","type"],"properties":{"target":{"type":"string","description":"Id of the thing reacted to.","example":"PST-4821"},"targetType":{"type":"string","enum":["post","comment","story","message"],"example":"post"},"type":{"type":"string","description":"The reaction.","example":"like"}}},"example":{"target":"PST-4821","targetType":"post","type":"like"}}}}}},"/client/community/reactions":{"get":{"operationId":"CommunitySocialClientController_getReactions","summary":"Get reactions on a target","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"target","required":true,"in":"query","schema":{"type":"string"},"description":"Id of the thing reacted to.","example":"PST-4821"},{"name":"targetType","required":false,"in":"query","schema":{"type":"string"},"example":"post"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Reactions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Who reacted to something, and how.\n\n#### Signature\n\n```http\nGET /client/community/reactions (target?: string, targetType?: string, limit?: integer, page?: integer) -> Reactions\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/react`"}},"/client/community/hashtags/trending":{"get":{"operationId":"CommunitySocialClientController_getTrendingHashtags","summary":"Get trending hashtags","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":10}],"responses":{"200":{"description":"Trending hashtags","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"The hashtags with the most recent activity.\n\n#### Signature\n\n```http\nGET /client/community/hashtags/trending (limit?: integer) -> Trending hashtags\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/feed`"}},"/client/community/stories":{"get":{"operationId":"CommunitySocialClientController_getStories","summary":"Get stories","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Community page id.","example":"PG-4821"}],"responses":{"200":{"description":"Stories","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Active stories, optionally for one page. Stories are short-lived by design — expired ones are not returned.\n\n#### Signature\n\n```http\nGET /client/community/stories (page?: string) -> Stories\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/stories`"},"post":{"operationId":"CommunitySocialClientController_createStory","summary":"Create a story","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The story","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/stories","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Publishes a story as the caller. Stories expire on their own; there is no need to delete them at the end of their life.\n\n#### Signature\n\n```http\nPOST /client/community/stories (body) -> The story\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/stories/{storyId}/view`","requestBody":{"description":"The story.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"media":"https://cdn.example.com/s/abc.jpg","caption":"On location today"}}}}}},"/client/community/stories/{storyId}/view":{"post":{"operationId":"CommunitySocialClientController_viewStory","summary":"Mark a story viewed","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"storyId","required":true,"in":"path","schema":{"type":"string"},"description":"Story id.","example":"STY-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/stories/{storyId}/view","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Records that the caller viewed a story — what populates the author's viewer list. The author can see who viewed, so this is not an anonymous action.\n\n#### Signature\n\n```http\nPOST /client/community/stories/{storyId}/view (storyId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The story author sees who viewed.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/stories`"}},"/client/community/stories/{storyId}":{"delete":{"operationId":"CommunitySocialClientController_deleteStory","summary":"Delete a story","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"storyId","required":true,"in":"path","schema":{"type":"string"},"description":"Story id.","example":"STY-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/stories/{storyId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not your story — The story belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not your story","path":"/client/community/stories/{storyId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Removes the caller's own story before it expires.\n\n#### Signature\n\n```http\nDELETE /client/community/stories/{storyId} (storyId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `403` | NOT_YOUR_STORY | Not your story | The story belongs to someone else. | Only the author can delete it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/stories`"}},"/client/community/follow":{"post":{"operationId":"CommunitySocialClientController_follow","summary":"Follow someone or something","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The follow","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/follow","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Follows a member, page or hashtag — `followingType` says which. Unlike a connection, following is one-way and needs no approval.\n\n#### Signature\n\n```http\nPOST /client/community/follow (body) -> The follow\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/community/follow/{followingId}`","requestBody":{"description":"What to follow.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["following","followingType"],"properties":{"following":{"type":"string","example":"ada@example.com"},"followingType":{"type":"string","enum":["member","page","hashtag"],"example":"member"}}},"example":{"following":"ada@example.com","followingType":"member"}}}}}},"/client/community/follow/{followingId}":{"delete":{"operationId":"CommunitySocialClientController_unfollow","summary":"Unfollow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"followingId","required":true,"in":"path","schema":{"type":"string"},"description":"What is being unfollowed.","example":"ada@example.com"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/follow/{followingId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Stops following a member, page or hashtag.\n\n#### Signature\n\n```http\nDELETE /client/community/follow/{followingId} (followingId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/following`"}},"/client/community/followers":{"get":{"operationId":"CommunitySocialClientController_getFollowers","summary":"List my followers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Followers","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/followers","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Who follows the caller.\n\n#### Signature\n\n```http\nGET /client/community/followers (limit?: integer, page?: integer) -> Followers\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/following`"}},"/client/community/following":{"get":{"operationId":"CommunitySocialClientController_getFollowing","summary":"List what I follow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"followingType","required":false,"in":"query","schema":{"type":"string"},"description":"`member`, `page` or `hashtag`.","example":"member"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Follows","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/following","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"What the caller follows, optionally narrowed to one kind.\n\n#### Signature\n\n```http\nGET /client/community/following (followingType?: string, limit?: integer, page?: integer) -> Follows\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/follow`"}},"/client/community/notifications":{"get":{"operationId":"CommunitySocialClientController_getNotifications","summary":"Get notifications","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"unread","required":false,"in":"query","schema":{"type":"boolean"},"example":true},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"description":"Filter by type."},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Notifications","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/notifications","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"The caller's community notifications. Pass `unread=true` for only the unseen ones.\n\n#### Signature\n\n```http\nGET /client/community/notifications (type?: string, unread?: boolean, limit?: integer, page?: integer) -> Notifications\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/notifications/read`"}},"/client/community/notifications/read":{"post":{"operationId":"CommunitySocialClientController_markNotificationsRead","summary":"Mark notifications read","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/notifications/read","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Marks specific notifications read by id.\n\n#### Signature\n\n```http\nPOST /client/community/notifications/read (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/notifications/read-all`","requestBody":{"description":"The notifications.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"example":["NTF-1","NTF-2"]}}},"example":{"ids":["NTF-1","NTF-2"]}}}}}},"/client/community/notifications/read-all":{"post":{"operationId":"CommunitySocialClientController_markAllNotificationsRead","summary":"Mark all notifications read","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/notifications/read-all","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Clears the caller's entire unread count in one call.\n\n#### Signature\n\n```http\nPOST /client/community/notifications/read-all () -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/notifications/unread-count`"}},"/client/community/notifications/unread-count":{"get":{"operationId":"CommunitySocialClientController_getUnreadCount","summary":"Get the unread notification count","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The count","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"count":7}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/notifications/unread-count","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"The badge count for community notifications.\n\n#### Signature\n\n```http\nGET /client/community/notifications/unread-count () -> The count\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/notifications`"}},"/client/community/bookmarks":{"get":{"operationId":"CommunitySocialClientController_getBookmarks","summary":"Get my bookmarks","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"targetType","required":false,"in":"query","schema":{"type":"string"},"example":"post"},{"name":"collection","required":false,"in":"query","schema":{"type":"string"},"example":"read-later"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Bookmarks","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/bookmarks","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"What the caller has saved, optionally by type or collection.\n\n#### Signature\n\n```http\nGET /client/community/bookmarks (targetType?: string, collection?: string, limit?: integer, page?: integer) -> Bookmarks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/bookmarks`"},"post":{"operationId":"CommunitySocialClientController_bookmark","summary":"Bookmark an item","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The bookmark","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/bookmarks","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Saves a post or other item to the caller's bookmarks. `collection` groups saves; `notes` is private to the caller.\n\n#### Signature\n\n```http\nPOST /client/community/bookmarks (body) -> The bookmark\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/community/bookmarks/{target}`","requestBody":{"description":"What to save.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["target","targetType"],"properties":{"target":{"type":"string","example":"PST-4821"},"targetType":{"type":"string","example":"post"},"collection":{"type":"string","example":"read-later"},"notes":{"type":"string","example":"Lens settings worth trying"}}},"example":{"target":"PST-4821","targetType":"post","collection":"read-later"}}}}}},"/client/community/bookmarks/{target}":{"delete":{"operationId":"CommunitySocialClientController_removeBookmark","summary":"Remove a bookmark","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"target","required":true,"in":"path","schema":{"type":"string"},"description":"Id of the bookmarked item.","example":"PST-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/bookmarks/{target}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Removes a saved item. The path segment is the **target id** — the thing bookmarked, not the bookmark record.\n\n#### Signature\n\n```http\nDELETE /client/community/bookmarks/{target} (target: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/bookmarks`"}},"/client/community/pages/{pageId}/announcements":{"get":{"operationId":"CommunitySocialClientController_getAnnouncements","summary":"Get page announcements","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Announcements","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}/announcements","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Announcements posted to a page.\n\n#### Signature\n\n```http\nGET /client/community/pages/{pageId}/announcements (pageId: string, limit?: integer, page?: integer) -> Announcements\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /client/community/pages/{pageId}/announcements`"},"post":{"operationId":"CommunitySocialClientController_createAnnouncement","summary":"Create an announcement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"pageId","required":true,"in":"path","schema":{"type":"string"},"description":"Page id.","example":"PG-4821"}],"responses":{"201":{"description":"The announcement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/pages/{pageId}/announcements","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Page not found — No community page has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Page not found","path":"/client/community/pages/{pageId}/announcements","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Posts an announcement to a page. Announcements typically notify every page member, so this reaches people — write it before sending, not after.\n\n#### Signature\n\n```http\nPOST /client/community/pages/{pageId}/announcements (pageId: string, body) -> The announcement\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Notifies page members.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | PAGE_NOT_FOUND | Page not found | No community page has that id. | List pages with `GET /client/community/pages`. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/pages/{pageId}/announcements`","requestBody":{"description":"The announcement.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"title":"Meetup moved to Thursday","content":"Same time, new room."}}}}}},"/client/community/announcements/{announcementId}":{"get":{"operationId":"CommunitySocialClientController_getAnnouncement","summary":"Get an announcement","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"announcementId","required":true,"in":"path","schema":{"type":"string"},"description":"Announcement id.","example":"ANN-4821"}],"responses":{"200":{"description":"The announcement","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Announcement not found — No announcement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Announcement not found","path":"/client/community/announcements/{announcementId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"One announcement.\n\n#### Signature\n\n```http\nGET /client/community/announcements/{announcementId} (announcementId: string) -> The announcement\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ANNOUNCEMENT_NOT_FOUND | Announcement not found | No announcement has that id. | Check the id. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/pages/{pageId}/announcements`"}},"/client/community/people":{"get":{"operationId":"CommunitySocialClientController_searchPeople","summary":"Search people","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"q","required":false,"in":"query","schema":{"type":"string"},"description":"Search text.","example":"ada"},{"name":"page","required":false,"in":"query","schema":{"type":"string"},"description":"Community page id. Without it the search returns nothing.","example":"PG-4821"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"pageNum","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Matching people","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"data":[],"total":0}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Searches members. With `page` set it searches that page's members; **without `page` it currently returns an empty result** — global people search is not implemented, the handler returns `{ data: [], total: 0 }` unconditionally. Pass a `page` to get real results.\n\n#### Signature\n\n```http\nGET /client/community/people (q?: string, page?: string, limit?: integer, pageNum?: integer) -> Matching people\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n- Global search (no `page`) always returns an empty list.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/pages/{pageId}/members`"}},"/client/community/people/suggestions":{"get":{"operationId":"CommunitySocialClientController_getPeopleSuggestions","summary":"Get people suggestions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":10}],"responses":{"200":{"description":"Always empty","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"data":[],"total":0}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/people/suggestions","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"People the caller might want to connect with.\n\n**Two defects to be aware of.** The handler is a stub that always returns `{ data: [], total: 0 }`. And it is declared *after* `GET /client/community/people/{email}`, so the request matches that route first with `email` = `suggestions` — a call here resolves as a profile lookup for a member named \"suggestions\" and returns `{ error: \"Not found\" }`. Neither path yields suggestions.\n\n#### Signature\n\n```http\nGET /client/community/people/suggestions (limit?: integer) -> Always empty\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Unreachable: shadowed by `GET /client/community/people/{email}`.\n- Stub — returns an empty list.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/people`"}},"/client/community/people/{email}":{"get":{"operationId":"CommunitySocialClientController_getPersonByEmail","summary":"Get a public profile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Member email address.","example":"ada@example.com"}],"responses":{"200":{"description":"The public profile, or `{ error: \"Not found\" }`","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"image":{"type":"string"},"company":{"type":"string"},"jobTitle":{"type":"string"},"bio":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"interests":{"type":"array","items":{"type":"string"}},"social":{"type":"object","additionalProperties":true}}},"example":{"email":"ada@example.com","firstName":"Ada","lastName":"Lovelace","company":"Acme","jobTitle":"Engineer","city":"London","country":"GB"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Returns a member's **public** profile by email address — name, image, company, job title, bio, city, country, interests and social links. Deliberately narrow: contact details and account fields are not included.\n\nTwo things to know. It is unauthenticated, so anyone with an email address and the org id can check whether that person is a member. And an unknown email returns **HTTP 200 with `{ \"error\": \"Not found\" }`**, not a 404 — check the body, not the status.\n\n#### Signature\n\n```http\nGET /client/community/people/{email} (email: string) -> The public profile, or `{ error: \"Not found\" }`\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n- A missing member returns 200 with an `error` field, not 404.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/people`"}},"/client/community/groups":{"post":{"operationId":"CommunitySocialClientController_createGroup","summary":"Create a group chat","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The group","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/groups","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Creates a group conversation with the caller as a member. Distinct from a community page — a group is a chat, not a space with a feed.\n\n#### Signature\n\n```http\nPOST /client/community/groups (body) -> The group\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/groups/{groupId}/messages`","requestBody":{"description":"The group.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Photo walk planning","members":["ada@example.com"]}}}}},"get":{"operationId":"CommunitySocialClientController_getMyGroups","summary":"List my group chats","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"pageNum","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based)."}],"responses":{"200":{"description":"Groups","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/groups","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Groups the caller belongs to.\n\n#### Signature\n\n```http\nGET /client/community/groups (pageNum?: integer, page?: integer, limit?: integer) -> Groups\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/groups/{groupId}`"}},"/client/community/groups/{groupId}":{"get":{"operationId":"CommunitySocialClientController_getGroup","summary":"Get a group chat","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"groupId","required":true,"in":"path","schema":{"type":"string"},"description":"Group id.","example":"GRP-4821"}],"responses":{"200":{"description":"The group","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/groups/{groupId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Group not found — No group has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Group not found","path":"/client/community/groups/{groupId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"One group with its members.\n\n#### Signature\n\n```http\nGET /client/community/groups/{groupId} (groupId: string) -> The group\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | GROUP_NOT_FOUND | Group not found | No group has that id. | List your groups. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/groups/{groupId}/messages`"}},"/client/community/groups/{groupId}/messages":{"post":{"operationId":"CommunitySocialClientController_sendGroupMessage","summary":"Send a group message","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"groupId","required":true,"in":"path","schema":{"type":"string"},"description":"Group id.","example":"GRP-4821"}],"responses":{"201":{"description":"The message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/groups/{groupId}/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Group not found — No group has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Group not found","path":"/client/community/groups/{groupId}/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Posts a message to a group chat as the caller.\n\n#### Signature\n\n```http\nPOST /client/community/groups/{groupId}/messages (groupId: string, body) -> The message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `404` | GROUP_NOT_FOUND | Group not found | No group has that id. | Check the id. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/groups/{groupId}/messages`","requestBody":{"description":"The message.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"content":"Saturday at 9 works for me."}}}}},"get":{"operationId":"CommunitySocialClientController_getGroupMessages","summary":"Get group messages","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"groupId","required":true,"in":"path","schema":{"type":"string"},"description":"Group id.","example":"GRP-4821"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"before","required":false,"in":"query","schema":{"type":"string"},"description":"Return messages older than this timestamp.","example":"2026-08-29T10:00:00.000Z"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/groups/{groupId}/messages","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Messages in a group, newest first. `before` pages backwards through history.\n\n#### Signature\n\n```http\nGET /client/community/groups/{groupId}/messages (groupId: string, limit?: integer, before?: string, page?: integer) -> Messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/groups/{groupId}/messages`"}},"/client/community/groups/{groupId}/members":{"post":{"operationId":"CommunitySocialClientController_addGroupMember","summary":"Add a group member","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"groupId","required":true,"in":"path","schema":{"type":"string"},"description":"Group id.","example":"GRP-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/groups/{groupId}/members","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Adds someone to a group chat. They gain access to the conversation — including, depending on the group's settings, messages already sent.\n\n#### Signature\n\n```http\nPOST /client/community/groups/{groupId}/members (groupId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The new member may see prior messages.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/community/groups/{groupId}/members/{email}`","requestBody":{"description":"Who to add.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","example":"ada@example.com"}}},"example":{"email":"ada@example.com"}}}}}},"/client/community/groups/{groupId}/members/{email}":{"delete":{"operationId":"CommunitySocialClientController_removeGroupMember","summary":"Remove a group member or leave","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"groupId","required":true,"in":"path","schema":{"type":"string"},"description":"Group id.","example":"GRP-4821"},{"name":"email","required":true,"in":"path","schema":{"type":"string"},"description":"Member to remove; the caller's own email to leave.","example":"ada@example.com"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/groups/{groupId}/members/{email}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Removes someone from a group. Passing the caller's own email is how they leave the group themselves.\n\n#### Signature\n\n```http\nDELETE /client/community/groups/{groupId}/members/{email} (groupId: string, email: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /client/community/groups/{groupId}/members`"}},"/client/community/badges":{"get":{"operationId":"CommunitySocialClientController_getBadges","summary":"List badges","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Badges","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"The badges defined for the community and what earns them.\n\n#### Signature\n\n```http\nGET /client/community/badges () -> Badges\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated — anyone with the org id can read this.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /client/community/badges/mine`"}},"/client/community/badges/mine":{"get":{"operationId":"CommunitySocialClientController_getMyBadges","summary":"Get my badges","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Badges","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/badges/mine","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Badges the caller has earned.\n\n#### Signature\n\n```http\nGET /client/community/badges/mine () -> Badges\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/badges`"}},"/client/community/connections/request":{"post":{"operationId":"CommunitySocialClientController_sendConnectionRequest","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The pending request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Cannot connect with yourself — `targetId` is the caller.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Cannot connect with yourself","path":"/client/community/connections/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections/request","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Send a connection request","description":"Asks another member to connect. The request is pending until they respond — connecting is mutual, so nothing is visible to either side as a connection until it is accepted.\n\nSelf-requests, duplicates and requests to an already-connected member are all refused rather than silently ignored.\n\n#### Signature\n\n```http\nPOST /client/community/connections/request (body) -> The pending request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `400` | SELF_CONNECT | Cannot connect with yourself | `targetId` is the caller. | Pick another member. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /community/connections/{id}/respond`","requestBody":{"description":"Who to connect with.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["targetId"],"properties":{"targetId":{"type":"string","example":"MEM-7712"},"message":{"type":"string","example":"We met at the conference last week."},"context":{"type":"object","description":"Where the request originated — an event, a group.","additionalProperties":true}}},"example":{"targetId":"MEM-7712","message":"We met at the conference last week."}}}}}},"/client/community/connections/{id}/respond":{"put":{"operationId":"CommunitySocialClientController_respondToConnection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Connection request id.","example":"CON-4821"}],"responses":{"200":{"description":"The updated connection","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to respond to this request — The caller is not the recipient.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to respond to this request","path":"/client/community/connections/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Connection request not found — No request has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Connection request not found","path":"/client/community/connections/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Respond to a connection request","description":"Accepts or rejects a request. Only the **recipient** may respond — the sender gets a 403.\n\n#### Signature\n\n```http\nPUT /client/community/connections/{id}/respond (id: string, body) -> The updated connection\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | REQUEST_NOT_FOUND | Connection request not found | No request has that id. | Read the pending list. |\n| `403` | NOT_RECIPIENT | Not authorized to respond to this request | The caller is not the recipient. | Only the recipient can accept or reject. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/pending`","requestBody":{"description":"The response.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["accept","reject"],"example":"accept"}}},"example":{"action":"accept"}}}}}},"/client/community/connections":{"get":{"operationId":"CommunitySocialClientController_getConnections","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"accepted"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"example":0},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based).","example":1}],"responses":{"200":{"description":"Connections","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"List connections","description":"The caller's connections, optionally filtered by status.\n\n#### Signature\n\n```http\nGET /client/community/connections (status?: string, limit?: integer, offset?: integer, page?: integer) -> Connections\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/stats`"}},"/client/community/connections/pending":{"get":{"operationId":"CommunitySocialClientController_getPendingRequests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based).","example":1}],"responses":{"200":{"description":"Pending requests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections/pending","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"List pending requests received","description":"Requests waiting on the caller's response — their inbox.\n\n#### Signature\n\n```http\nGET /client/community/connections/pending (limit?: integer, page?: integer) -> Pending requests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/sent`"}},"/client/community/connections/sent":{"get":{"operationId":"CommunitySocialClientController_getSentRequests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":20},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based).","example":1}],"responses":{"200":{"description":"Sent requests","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections/sent","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"List pending requests sent","description":"Requests the caller has sent that are still unanswered.\n\n#### Signature\n\n```http\nGET /client/community/connections/sent (limit?: integer, page?: integer) -> Sent requests\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/pending`"}},"/client/community/connections/stats":{"get":{"operationId":"CommunitySocialClientController_getConnectionStats","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections/stats","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Get connection statistics","description":"Counts of connections, pending requests in and out.\n\n#### Signature\n\n```http\nGET /client/community/connections/stats () -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections`"}},"/client/community/connections/accept-all":{"post":{"operationId":"CommunitySocialClientController_acceptAllRequests","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was accepted","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections/accept-all","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Accept all pending requests","description":"Accepts every request currently waiting on the caller in one call. Convenient, but indiscriminate — it connects the caller to everyone in the queue, including anyone they would have rejected. Review the pending list first.\n\n#### Signature\n\n```http\nPOST /client/community/connections/accept-all () -> What was accepted\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Accepts everything pending, without review.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/connections/pending`"}},"/client/community/connections/{id}":{"delete":{"operationId":"CommunitySocialClientController_removeConnection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Connection id.","example":"CON-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/connections/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized — The caller is not part of the connection.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized","path":"/client/community/connections/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Connection not found — No connection has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Connection not found","path":"/client/community/connections/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Remove a connection","description":"Disconnects from a member, or withdraws a request the caller sent. The connection disappears for both sides.\n\n#### Signature\n\n```http\nDELETE /client/community/connections/{id} (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | CONNECTION_NOT_FOUND | Connection not found | No connection has that id. | Check the id. |\n| `403` | NOT_AUTHORIZED | Not authorized | The caller is not part of the connection. | Only participants can remove it. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /community/blocks`"}},"/client/community/messages":{"post":{"operationId":"CommunitySocialClientController_sendMessage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The sent message","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Cannot send message to this user — A block exists in either direction, or the recipient restricts messages.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Cannot send message to this user","path":"/client/community/messages","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Send a direct message","description":"Sends a message from the caller to another member. Blocks are enforced here — if either side has blocked the other the send is refused with 403, which is how blocking actually stops contact.\n\n#### Signature\n\n```http\nPOST /client/community/messages (body) -> The sent message\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `403` | CANNOT_MESSAGE | Cannot send message to this user | A block exists in either direction, or the recipient restricts messages. | Nothing to retry. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/thread/{userId}`","requestBody":{"description":"The message.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["recipientId","content"],"properties":{"recipientId":{"type":"string","example":"MEM-7712"},"content":{"type":"string","example":"Are you free Thursday?"},"contentType":{"type":"string","description":"Defaults to plain text.","example":"text"},"attachments":{"type":"array","items":{"type":"object","additionalProperties":true}},"replyTo":{"type":"string","description":"Message being replied to.","example":"MSG-4820"},"context":{"type":"object","additionalProperties":true}}},"example":{"recipientId":"MEM-7712","content":"Are you free Thursday?"}}}}}},"/client/community/messages/threads":{"get":{"operationId":"CommunitySocialClientController_getThreads","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"offset","required":false,"in":"query","schema":{"type":"integer"},"example":0},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based).","example":1}],"responses":{"200":{"description":"Threads","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/messages/threads","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"List conversation threads","description":"The caller's conversations with their most recent message — the messaging inbox.\n\n#### Signature\n\n```http\nGET /client/community/messages/threads (limit?: integer, offset?: integer, page?: integer) -> Threads\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/thread/{userId}`"}},"/client/community/messages/thread/{userId}":{"get":{"operationId":"CommunitySocialClientController_getThread","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The other member.","example":"MEM-7712"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"before","required":false,"in":"query","schema":{"type":"string"},"description":"Return messages older than this timestamp.","example":"2026-08-29T10:00:00.000Z"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based).","example":1}],"responses":{"200":{"description":"Messages","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/messages/thread/{userId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Get a conversation","description":"Messages exchanged with one member, newest first. `before` pages backwards through history — pass the oldest message's timestamp from the previous page.\n\n#### Signature\n\n```http\nGET /client/community/messages/thread/{userId} (userId: string, limit?: integer, before?: string, page?: integer) -> Messages\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/messages/thread/{userId}/read`"}},"/client/community/messages/read":{"post":{"operationId":"CommunitySocialClientController_markAsRead","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/messages/read","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Mark messages read","description":"Marks specific messages as read by id. Use the thread form to clear a whole conversation.\n\n#### Signature\n\n```http\nPOST /client/community/messages/read (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/messages/thread/{userId}/read`","requestBody":{"description":"The messages.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["messageIds"],"properties":{"messageIds":{"type":"array","items":{"type":"string"},"example":["MSG-4820","MSG-4821"]}}},"example":{"messageIds":["MSG-4820","MSG-4821"]}}}}}},"/client/community/messages/thread/{userId}/read":{"post":{"operationId":"CommunitySocialClientController_markThreadAsRead","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The other member.","example":"MEM-7712"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/messages/thread/{userId}/read","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Mark a conversation read","description":"Clears the unread state for an entire conversation — what a client calls when the thread is opened.\n\n#### Signature\n\n```http\nPOST /client/community/messages/thread/{userId}/read (userId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/unread-count`"}},"/client/community/messages/{id}":{"delete":{"operationId":"CommunitySocialClientController_deleteMessage","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Message id.","example":"MSG-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/messages/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized — The message is not the caller's.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized","path":"/client/community/messages/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Message not found — No message has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Message not found","path":"/client/community/messages/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Delete a message","description":"Deletes one of the caller's own messages. Someone else's message cannot be deleted — that returns 403.\n\n#### Signature\n\n```http\nDELETE /client/community/messages/{id} (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MESSAGE_NOT_FOUND | Message not found | No message has that id. | Check the id. |\n| `403` | NOT_AUTHORIZED | Not authorized | The message is not the caller's. | Only the sender can delete. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /community/reports`"}},"/client/community/messages/unread-count":{"get":{"operationId":"CommunitySocialClientController_getMessageUnreadCount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The count","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"count":3}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/messages/unread-count","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Get the unread message count","description":"How many unread messages the caller has — the badge count.\n\n#### Signature\n\n```http\nGET /client/community/messages/unread-count () -> The count\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/messages/threads`"}},"/client/community/meetings":{"post":{"operationId":"CommunitySocialClientController_createMeeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"At least one participant is required — `participantIds` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"At least one participant is required","path":"/client/community/meetings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/meetings","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Create a meeting","description":"Schedules a meeting between the caller and one or more participants. The caller becomes the organiser, and only the organiser can later edit it. Give either `endTime` or `duration`; `timezone` matters when participants are in different ones.\n\n#### Signature\n\n```http\nPOST /client/community/meetings (body) -> The meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `400` | PARTICIPANT_REQUIRED | At least one participant is required | `participantIds` is empty. | Invite someone. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}/respond`","requestBody":{"description":"The meeting.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["participantIds","startTime"],"properties":{"title":{"type":"string","example":"Intro call"},"participantIds":{"type":"array","items":{"type":"string"},"example":["MEM-7712"]},"startTime":{"type":"string","format":"date-time","example":"2026-09-03T14:00:00.000Z"},"endTime":{"type":"string","format":"date-time","example":"2026-09-03T14:30:00.000Z"},"duration":{"type":"integer","description":"Minutes. Alternative to `endTime`.","example":30},"timezone":{"type":"string","example":"America/New_York"},"locationType":{"type":"string","example":"virtual"},"location":{"type":"string","example":"Room 3"},"meetingLink":{"type":"string","example":"https://meet.example.com/abc"},"description":{"type":"string"},"event":{"type":"string","description":"Event this meeting belongs to."},"eventName":{"type":"string"}}},"example":{"title":"Intro call","participantIds":["MEM-7712"],"startTime":"2026-09-03T14:00:00.000Z","duration":30,"locationType":"virtual","meetingLink":"https://meet.example.com/abc"}}}}},"get":{"operationId":"CommunitySocialClientController_getMeetings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"accepted"},{"name":"fromDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-01"},{"name":"toDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-09-30"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"description":"Page number (1-based).","example":1}],"responses":{"200":{"description":"Meetings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/meetings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"List meetings","description":"Meetings the caller organises or attends, filterable by status and date range.\n\n#### Signature\n\n```http\nGET /client/community/meetings (status?: string, fromDate?: string, toDate?: string, limit?: integer, page?: integer) -> Meetings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/meetings/upcoming`"}},"/client/community/meetings/upcoming":{"get":{"operationId":"CommunitySocialClientController_getUpcomingMeetings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":5}],"responses":{"200":{"description":"Upcoming meetings","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/meetings/upcoming","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"List upcoming meetings","description":"The caller's next meetings — what a home screen shows.\n\n#### Signature\n\n```http\nGET /client/community/meetings/upcoming (limit?: integer) -> Upcoming meetings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/meetings`"}},"/client/community/meetings/{id}":{"get":{"operationId":"CommunitySocialClientController_getMeeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/meetings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to view this meeting — The caller is neither organiser nor participant.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to view this meeting","path":"/client/community/meetings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/client/community/meetings/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Get a meeting","description":"One meeting with its participants and their responses. Only participants may read it.\n\n#### Signature\n\n```http\nGET /client/community/meetings/{id} (id: string) -> The meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | NOT_PARTICIPANT_VIEW | Not authorized to view this meeting | The caller is neither organiser nor participant. | Ask the organiser to invite you. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}/respond`"},"put":{"operationId":"CommunitySocialClientController_updateMeeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The updated meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/meetings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Only organizer can update the meeting — The caller is a participant, not the organiser.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only organizer can update the meeting","path":"/client/community/meetings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/client/community/meetings/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Update a meeting","description":"Changes a meeting's details. **Organiser only** — participants get a 403. Moving the time re-opens the RSVPs, so participants who had accepted need to respond again.\n\n#### Signature\n\n```http\nPUT /client/community/meetings/{id} (id: string, body) -> The updated meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | ORGANIZER_ONLY | Only organizer can update the meeting | The caller is a participant, not the organiser. | Ask the organiser to make the change. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `DELETE /community/meetings/{id}`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"location":{"type":"string"},"meetingLink":{"type":"string"},"description":{"type":"string"}}},"example":{"startTime":"2026-09-03T15:00:00.000Z"}}}}},"delete":{"operationId":"CommunitySocialClientController_cancelMeeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/meetings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not authorized to cancel this meeting — The caller is not the organiser.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not authorized to cancel this meeting","path":"/client/community/meetings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/client/community/meetings/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Cancel a meeting","description":"Cancels a meeting and notifies its participants. Organiser only. A `reason` is passed on to the participants, so it is worth writing one.\n\n#### Signature\n\n```http\nDELETE /client/community/meetings/{id} (id: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Notifies participants.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | NOT_AUTHORIZED_CANCEL | Not authorized to cancel this meeting | The caller is not the organiser. | Only the organiser can cancel. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}`","requestBody":{"description":"Optional reason, shown to participants.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Rescheduling to next week"}}},"example":{"reason":"Rescheduling to next week"}}}}}},"/client/community/meetings/{id}/respond":{"put":{"operationId":"CommunitySocialClientController_respondToMeeting","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Meeting id.","example":"MTG-4821"}],"responses":{"200":{"description":"The updated meeting","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/meetings/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Not a participant of this meeting — The caller was not invited.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Not a participant of this meeting","path":"/client/community/meetings/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Meeting not found — No meeting has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Meeting not found","path":"/client/community/meetings/{id}/respond","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Respond to a meeting invitation","description":"Records the caller's RSVP. Only an invited participant can respond.\n\n#### Signature\n\n```http\nPUT /client/community/meetings/{id}/respond (id: string, body) -> The updated meeting\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | MEETING_NOT_FOUND | Meeting not found | No meeting has that id. | Check the id. |\n| `403` | NOT_PARTICIPANT | Not a participant of this meeting | The caller was not invited. | Only invitees can respond. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `PUT /community/meetings/{id}`","requestBody":{"description":"The response.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["response"],"properties":{"response":{"type":"string","enum":["accepted","declined","tentative"],"example":"accepted"}}},"example":{"response":"accepted"}}}}}},"/client/community/blocks":{"post":{"operationId":"CommunitySocialClientController_blockUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The block","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/blocks","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Block a member","description":"Blocks another member. The block is enforced at the messaging layer in **both** directions — neither side can message the other afterwards, regardless of who blocked whom.\n\nSetting `report: true` files a moderation report at the same time, which is the right choice when the behaviour warrants review rather than only personal avoidance.\n\n#### Signature\n\n```http\nPOST /client/community/blocks (body) -> The block\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /community/blocks/{userId}`\n- `POST /community/reports`","requestBody":{"description":"Who to block.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["blockedId"],"properties":{"blockedId":{"type":"string","example":"MEM-7712"},"reason":{"type":"string","example":"harassment"},"reasonDetails":{"type":"string"},"report":{"type":"boolean","description":"Also file a moderation report.","example":true},"reportDetails":{"type":"string"},"context":{"type":"object","additionalProperties":true}}},"example":{"blockedId":"MEM-7712","reason":"harassment","report":true,"reportDetails":"Repeated unsolicited messages after being asked to stop."}}}}},"get":{"operationId":"CommunitySocialClientController_getBlockedUsers","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Blocked members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/blocks","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"List blocked members","description":"Who the caller has blocked. Does not show who has blocked the caller — that is deliberately not disclosed.\n\n#### Signature\n\n```http\nGET /client/community/blocks () -> Blocked members\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/blocks`"}},"/client/community/blocks/{userId}":{"delete":{"operationId":"CommunitySocialClientController_unblockUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"userId","required":true,"in":"path","schema":{"type":"string"},"description":"The blocked member.","example":"MEM-7712"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/blocks/{userId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Block not found — That member is not blocked.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Block not found","path":"/client/community/blocks/{userId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Unblock a member","description":"Lifts a block, allowing contact again. Any report filed alongside the block stands — unblocking does not withdraw it.\n\n#### Signature\n\n```http\nDELETE /client/community/blocks/{userId} (userId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n| `404` | BLOCK_NOT_FOUND | Block not found | That member is not blocked. | Read the block list. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /community/blocks`"}},"/client/community/reports":{"post":{"operationId":"CommunitySocialClientController_reportUser","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/reports","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community"],"summary":"Report a member","description":"Files a moderation report against another member. `details` is required and is what a moderator acts on — a report with no substance cannot be assessed.\n\nReporting does not block: to also stop contact, use `POST /community/blocks` with `report: true`, or block separately.\n\n#### Signature\n\n```http\nPOST /client/community/reports (body) -> The report\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Filing a report does not block the member.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints always act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /community/blocks`","requestBody":{"description":"The report.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["reportedId","reason","details"],"properties":{"reportedId":{"type":"string","example":"MEM-7712"},"reason":{"type":"string","example":"harassment"},"details":{"type":"string","example":"Sent repeated unsolicited messages after being asked to stop."},"context":{"type":"object","description":"Where it happened — a post, a thread.","additionalProperties":true}}},"example":{"reportedId":"MEM-7712","reason":"harassment","details":"Sent repeated unsolicited messages after being asked to stop."}}}}}},"/client/community/media/upload":{"post":{"operationId":"CommunitySocialClientController_uploadMedia","summary":"Upload media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The stored file","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"File is required — No `file` part was sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"File is required","path":"/client/community/media/upload","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/media/upload","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Uploads a file as `multipart/form-data` under the field name `file`, and returns its stored path for use in a post or story. The upload is attributed to the caller and counts against their own media library.\n\n#### Signature\n\n```http\nPOST /client/community/media/upload (body) -> The stored file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n| `400` | FILE_REQUIRED | File is required | No `file` part was sent. | Send multipart form data with a `file` field. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/media/mine`","requestBody":{"description":"Multipart form with a `file` field.","required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/client/community/media/mine":{"get":{"operationId":"CommunitySocialClientController_getMyMedia","summary":"List my media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"maxKeys","required":false,"in":"query","schema":{"type":"integer"},"description":"Maximum files to list."}],"responses":{"200":{"description":"Media","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/media/mine","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Files the caller has uploaded.\n\n#### Signature\n\n```http\nGET /client/community/media/mine (maxKeys?: integer, page?: integer) -> Media\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /client/community/media/rename`"}},"/client/community/media/rename":{"put":{"operationId":"CommunitySocialClientController_renameMedia","summary":"Rename media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/media/rename","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Renames one of the caller's files. Anything already referencing the old path — a published post, a story — keeps pointing at the old name and may break.\n\n#### Signature\n\n```http\nPUT /client/community/media/rename (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing references are not rewritten.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /client/community/media/{path}`","requestBody":{"description":"Old and new path.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"path":"photos/img1.jpg","newName":"photos/lens-test.jpg"}}}}}},"/client/community/media/{path}":{"delete":{"operationId":"CommunitySocialClientController_deleteMedia","summary":"Delete media","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"path","required":true,"in":"path","schema":{"type":"string"},"description":"Stored file path.","example":"photos/img1.jpg"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Not authenticated — No member could be resolved from the token.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Not authenticated","path":"/client/community/media/{path}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Community · Client"],"description":"Deletes one of the caller's files. Posts and stories that embed it will show a broken reference — check where it is used before deleting.\n\n#### Signature\n\n```http\nDELETE /client/community/media/{path} (path: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Breaks any post or story embedding the file.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | NOT_AUTHENTICATED | Not authenticated | No member could be resolved from the token. | Sign in; these endpoints act as the caller. |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /client/community/media/mine`"}},"/social/topics":{"get":{"operationId":"SocialTopicController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Topics","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List social topics","description":"Topic definitions — the rules that classify incoming social activity into subject areas.\n\n#### Signature\n\n```http\nGET /social/topics () -> Topics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /social/topics/test`","tags":["Community · Topics"]},"post":{"operationId":"SocialTopicController_create","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The topic","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a social topic","description":"Defines a topic and its matching rules. New topics apply to activity arriving from now on — run a backfill to classify what already exists.\n\n#### Signature\n\n```http\nPOST /social/topics (body) -> The topic\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /social/topics/{id}/backfill`","tags":["Community · Topics"],"requestBody":{"description":"The topic.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"Pricing complaints","keywords":["too expensive","price increase"],"platforms":["twitter","facebook"]}}}}}},"/social/topics/{id}":{"get":{"operationId":"SocialTopicController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Topic id.","example":"TOP-12"}],"responses":{"200":{"description":"The topic","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a social topic","description":"One topic with its matching rules.\n\n#### Signature\n\n```http\nGET /social/topics/{id} (id: string) -> The topic\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /social/topics/{id}`","tags":["Community · Topics"]},"put":{"operationId":"SocialTopicController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Topic id.","example":"TOP-12"}],"responses":{"200":{"description":"The updated topic","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update a social topic","description":"Changes a topic's rules. Activity already classified keeps its previous assignment until a backfill re-runs the new rules over it.\n\n#### Signature\n\n```http\nPUT /social/topics/{id} (id: string, body) -> The updated topic\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing classifications are not revisited automatically.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /social/topics/{id}/backfill`","tags":["Community · Topics"],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"keywords":["too expensive","price increase","cost"]}}}}},"delete":{"operationId":"SocialTopicController_remove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Topic id.","example":"TOP-12"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a social topic","description":"Removes a topic definition. Activity already tagged with it keeps the tag, which then refers to a topic that no longer exists.\n\n#### Signature\n\n```http\nDELETE /social/topics/{id} (id: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /social/topics`","tags":["Community · Topics"]}},"/social/topics/test":{"post":{"operationId":"SocialTopicController_test","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"201":{"description":"Whether it matched, and why","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Test a topic against a sample","description":"Runs a candidate topic definition against one sample piece of content and reports whether it would match. Nothing is saved — the way to tune rules before creating the topic.\n\n#### Signature\n\n```http\nPOST /social/topics/test (body) -> Whether it matched, and why\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Dry run — nothing is stored.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /social/topics`","tags":["Community · Topics"],"requestBody":{"description":"The candidate topic and a sample.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"topic":{"type":"object","description":"Topic definition to test.","additionalProperties":true},"sample":{"type":"object","properties":{"content":{"type":"string","example":"This is way too expensive now."},"title":{"type":"string"},"platform":{"type":"string","example":"twitter"},"standardActivityType":{"type":"string","example":"post"}}}}},"example":{"topic":{"keywords":["too expensive"]},"sample":{"content":"This is way too expensive now.","platform":"twitter"}}}}}}},"/social/topics/backfill":{"post":{"operationId":"SocialTopicController_backfillAll","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The backfill result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Backfill all topics","description":"Re-runs **every** topic's rules over the last `days` of activity. Reprocesses the whole window across all topics, so it is considerably heavier than the single-topic form — prefer that one after editing a single topic.\n\n#### Signature\n\n```http\nPOST /social/topics/backfill (body) -> The backfill result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Heavy — reprocesses all topics over the window.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /social/topics/{id}/backfill`","tags":["Community · Topics"],"requestBody":{"description":"How far back to go.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","example":30}}},"example":{"days":30}}}}}},"/social/topics/{id}/backfill":{"post":{"operationId":"SocialTopicController_backfillOne","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Topic id.","example":"TOP-12"}],"responses":{"201":{"description":"The backfill result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Backfill one topic","description":"Re-runs one topic's rules over the last `days` of social activity, tagging what matches. Run this after changing a topic's rules so historic activity reflects them.\n\n#### Signature\n\n```http\nPOST /social/topics/{id}/backfill (id: string, body) -> The backfill result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /social/topics/backfill`","tags":["Community · Topics"],"requestBody":{"description":"How far back to go.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","description":"Days of history to reprocess.","example":30}}},"example":{"days":30}}}}}},"/notes/{datatype}/{id}":{"post":{"operationId":"NotesController_addNote","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The note","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notes"],"summary":"Add a note to a record","description":"Attaches an internal note to any record. Notes are staff-facing annotation — unlike comments, they are not part of a public conversation and carry no rating or threading.\n\n#### Signature\n\n```http\nPOST /notes/{datatype}/{id} (datatype: string, id: string, body) -> The note\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /notes/{datatype}/{id}`","requestBody":{"description":"The note.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"type":"string","example":"Customer called about this order; promised a callback Thursday."}}},"example":{"comment":"Customer called about this order; promised a callback Thursday."}}}}},"get":{"operationId":"NotesController_listNotes","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"200":{"description":"Notes","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Notes"],"summary":"List a record's notes","description":"The internal notes on a record, oldest first.\n\n#### Signature\n\n```http\nGET /notes/{datatype}/{id} (datatype: string, id: string) -> Notes\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /notes/{datatype}/{id}`"}},"/handling/{datatype}/{id}":{"get":{"operationId":"HandlingController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The record's collection, e.g. ticket, message, social_activity, sf_order.","example":"ticket"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"The record id (sk).","example":"6ab5f00bbca07825d6de3159"}],"responses":{"200":{"description":"The handling, or null","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Handling"],"summary":"Who is dealing with a record","description":"The record's handling: status (open, in_progress, done, no_action), who has it, since when, the outcome, and its history. Null when nothing tracks it.\n\n#### Signature\n\n```http\nGET /handling/{datatype}/{id} (datatype: string, id: string) -> The handling, or null\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/handling/{datatype}/{id}/take":{"post":{"operationId":"HandlingController_take","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The record's collection, e.g. ticket, message, social_activity, sf_order.","example":"ticket"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"The record id (sk).","example":"6ab5f00bbca07825d6de3159"}],"responses":{"201":{"description":"{ taken, handling, message? }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Handling"],"summary":"Take a record","description":"Take it before working on it. Succeeds only while nobody has it — two taking at once, one wins. Returns { taken: true } or { taken: false, message } naming who has it; then leave it.\n\n#### Signature\n\n```http\nPOST /handling/{datatype}/{id}/take (datatype: string, id: string) -> { taken, handling, message? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /handling/{datatype}/{id}/done`"}},"/handling/{datatype}/{id}/done":{"post":{"operationId":"HandlingController_done","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The record's collection, e.g. ticket, message, social_activity, sf_order.","example":"ticket"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"The record id (sk).","example":"6ab5f00bbca07825d6de3159"}],"responses":{"201":{"description":"{ handling }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Handling"],"summary":"Finish a record","description":"It is dealt with. `outcome` is one line on what was done.\n\n#### Signature\n\n```http\nPOST /handling/{datatype}/{id}/done (datatype: string, id: string, body) -> { handling }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"What was done.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"outcome":"Replied with opening hours."}}}}}},"/handling/{datatype}/{id}/no-action":{"post":{"operationId":"HandlingController_noAction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The record's collection, e.g. ticket, message, social_activity, sf_order.","example":"ticket"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"The record id (sk).","example":"6ab5f00bbca07825d6de3159"}],"responses":{"201":{"description":"{ handling }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Handling"],"summary":"Close a record — nothing needed","description":"Nothing needs doing (spam, already answered…). `reason` says why.\n\n#### Signature\n\n```http\nPOST /handling/{datatype}/{id}/no-action (datatype: string, id: string, body) -> { handling }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"Why nothing is needed.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Duplicate of another ticket."}}}}}},"/handling/{datatype}/{id}/release":{"post":{"operationId":"HandlingController_release","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"The record's collection, e.g. ticket, message, social_activity, sf_order.","example":"ticket"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"The record id (sk).","example":"6ab5f00bbca07825d6de3159"}],"responses":{"201":{"description":"{ released, handling }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Handling"],"summary":"Give a record back","description":"Open it again for anyone to take — when you cannot finish it, or to reopen a finished one.\n\n#### Signature\n\n```http\nPOST /handling/{datatype}/{id}/release (datatype: string, id: string, body) -> { released, handling }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"Optional reason.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"Needs someone with billing access."}}}}}},"/comments/{datatype}/{id}":{"post":{"operationId":"CommentsController_create","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The comment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"Add a comment","description":"Comments on any record. `parentComment` makes it a reply; `rating` attaches a score, which is how a comment doubles as a review.\n\nThe author is the calling customer or user — comments cannot be posted on someone else's behalf.\n\n#### Signature\n\n```http\nPOST /comments/{datatype}/{id} (datatype: string, id: string, body) -> The comment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /comments/{datatype}/{id}`","requestBody":{"description":"The comment.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","example":"Arrived quickly and well packed."},"parentComment":{"type":"string","description":"Makes this a reply.","example":"CMT-4820"},"rating":{"type":"number","example":5},"title":{"type":"string","example":"Good service"}}},"example":{"message":"Arrived quickly and well packed.","rating":5}}}}},"get":{"operationId":"CommentsController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"},{"name":"sort","required":false,"in":"query","schema":{"type":"string"},"example":"newest"},{"name":"parent","required":false,"in":"query","schema":{"type":"string"},"description":"Fetch replies to this comment.","example":"CMT-4820"},{"name":"includeHidden","required":false,"in":"query","schema":{"type":"boolean"},"description":"Include moderated comments.","example":false},{"name":"p","in":"query","required":false,"description":"Page number.","schema":{"type":"integer"},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"},"example":20}],"responses":{"200":{"description":"Comments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"List comments on a record","description":"Comments on a record. `parent` fetches replies to a specific comment rather than the top level, and `includeHidden` shows moderated comments — a moderator view, since hidden comments are hidden for a reason.\n\n#### Signature\n\n```http\nGET /comments/{datatype}/{id} (datatype: string, id: string, p?: integer, ps?: integer, sort?: string, parent?: string, includeHidden?: boolean) -> Comments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- `includeHidden` exposes moderated content.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /comments/{commentId}/replies`"}},"/comments/{commentId}":{"put":{"operationId":"CommentsController_update","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-4821"}],"responses":{"200":{"description":"The updated comment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"Edit a comment","description":"Edits a comment. Restricted to its author — someone else's comment is moderated, not edited.\n\n#### Signature\n\n```http\nPUT /comments/{commentId} (commentId: string, body) -> The updated comment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /comments/{commentId}/status`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"message":"Arrived quickly and well packed. Updated: the lid was cracked."}}}}},"delete":{"operationId":"CommentsController_remove","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"Delete a comment","description":"Removes a comment. Replies to it may be orphaned — setting its status to hidden preserves the thread structure, which is usually the better moderation action.\n\n#### Signature\n\n```http\nDELETE /comments/{commentId} (commentId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Prefer hiding when the comment has replies.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /comments/{commentId}/status`"}},"/comments/single/{commentId}":{"get":{"operationId":"CommentsController_getOne","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-4821"}],"responses":{"200":{"description":"The comment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"Get one comment","description":"A single comment by id. The path segment `single` disambiguates it from the `{datatype}/{id}` list route.\n\n#### Signature\n\n```http\nGET /comments/single/{commentId} (commentId: string) -> The comment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /comments/{commentId}`"}},"/comments/{commentId}/replies":{"get":{"operationId":"CommentsController_replies","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-4821"},{"name":"sort","required":false,"in":"query","schema":{"type":"string"},"example":"oldest"},{"name":"p","in":"query","required":false,"description":"Page number.","schema":{"type":"integer"},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"},"example":20}],"responses":{"200":{"description":"Replies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"List replies to a comment","description":"The replies under one comment.\n\n#### Signature\n\n```http\nGET /comments/{commentId}/replies (commentId: string, p?: integer, ps?: integer, sort?: string) -> Replies\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /comments/{datatype}/{id}`"}},"/comments/{commentId}/pin":{"post":{"operationId":"CommentsController_pin","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-4821"}],"responses":{"201":{"description":"The comment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"Pin a comment","description":"Pins a comment to the top of its thread. `pinned: false` unpins — omitting the body pins, since it defaults to true.\n\n#### Signature\n\n```http\nPOST /comments/{commentId}/pin (commentId: string, body) -> The comment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /comments/{commentId}/status`","requestBody":{"description":"Whether to pin. Defaults to pinning.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"pinned":{"type":"boolean","example":true}}},"example":{"pinned":true}}}}}},"/comments/{commentId}/status":{"post":{"operationId":"CommentsController_status","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-4821"}],"responses":{"201":{"description":"The comment","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"invalid status — The status is missing or not a recognised value.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"invalid status","path":"/comments/{commentId}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"Set a comment's status","description":"Moderates a comment — approving, hiding or flagging it. This is what controls whether the public sees it, so it is the moderation action rather than deletion.\n\n#### Signature\n\n```http\nPOST /comments/{commentId}/status (commentId: string, body) -> The comment\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_STATUS | invalid status | The status is missing or not a recognised value. | Send a valid comment status. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /comments/{commentId}/report`","requestBody":{"description":"The new status.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"A valid comment status.","example":"hidden"}}},"example":{"status":"hidden"}}}}}},"/comments/{commentId}/report":{"post":{"operationId":"CommentsController_report","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"commentId","required":true,"in":"path","schema":{"type":"string"},"description":"Comment id.","example":"CMT-4821"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"Report a comment","description":"Flags a comment for moderator attention. Reporting does not hide it — a moderator decides.\n\n#### Signature\n\n```http\nPOST /comments/{commentId}/report (commentId: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /comments/{commentId}/status`","requestBody":{"description":"Why it is being reported.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"reason":"spam","details":"Repeated promotional links."}}}}}},"/comments/by-author/{author}":{"get":{"operationId":"CommentsController_byAuthor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"author","required":true,"in":"path","schema":{"type":"string"},"description":"Author identifier, typically an email.","example":"ada@example.com"},{"name":"p","in":"query","required":false,"description":"Page number.","schema":{"type":"integer"},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"},"example":20}],"responses":{"200":{"description":"Comments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Comments"],"summary":"List an author's comments","description":"Everything one author has commented, across records — useful when moderating a pattern rather than a single comment.\n\n#### Signature\n\n```http\nGET /comments/by-author/{author} (author: string, p?: integer, ps?: integer) -> Comments\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /comments/{commentId}/status`"}},"/content-studio/review":{"get":{"operationId":"ContentStudioController_reviewAll","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Review rows","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"enrollmentId":{"type":"string","description":"post_progress sk"},"postId":{"type":"string"},"itemId":{"type":"string"},"person":{"type":"object","description":"The post_progress owner","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"}}},"answerRef":{"type":"string"},"answer":{"type":"object","nullable":true,"additionalProperties":true,"description":"The values sent in, so the work itself is judged"},"submittedAt":{"type":"string","format":"date-time"}}}},"example":[{"enrollmentId":"66f2b1c0ffee","postId":"66f2a0c0ffee","itemId":"p-plan","person":{"datatype":"user","id":"66e0aa11","email":"ana@example.com","name":"Ana Ruiz"},"answerRef":"form_submission/66f3c2d0","answer":{"businessName":"Sunrise Café","allergens":["sesame"]},"submittedAt":"2026-09-22T10:14:03.000Z"}]}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"Waiting for review — all courses","description":"Every item waiting for a reviewer across the org’s posts, oldest first, each with the answer that was sent in. Looks at up to 500 active post_progress records.\n\n#### Signature\n\n```http\nGET /content-studio/review () -> Review rows\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /content-studio/enrollments/{id}/review/{itemId}`"}},"/content-studio/enrollments/{id}":{"get":{"operationId":"ContentStudioController_enrollment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"post_progress sk","example":"66f2b1c0ffee"}],"responses":{"200":{"description":"The course view plus { person, progress }","content":{"application/json":{"schema":{"type":"object","properties":{"course":{"type":"object","properties":{"id":{"type":"string","description":"post sk"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"coverImage":{"type":"object","additionalProperties":true},"dueInDays":{"type":"number"},"layout":{"type":"string","enum":["sidebar","steps"]},"navigation":{"type":"string","enum":["sidebar","top","none"]},"steps":{"type":"integer","description":"Number of pages"}}},"enrollment":{"type":"object","nullable":true,"description":"The caller’s post_progress, or null when not enrolled.","properties":{"id":{"type":"string","description":"post_progress sk"},"status":{"type":"string","enum":["active","completed","withdrawn"]},"enrolledAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"percent":{"type":"number"}}},"outline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Tree, as the sidebar shows it"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Flat, in reading order"},"next":{"type":"string","nullable":true,"description":"The first open page not yet done"},"person":{"type":"object","description":"The post_progress owner","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"}}},"progress":{"type":"object","description":"Keyed by outline item id","additionalProperties":{"type":"object","properties":{"done":{"type":"boolean"},"at":{"type":"string","format":"date-time"},"score":{"type":"number","description":"0–100. For pages with an answer key, only the server sets it."},"percent":{"type":"number","description":"Watched % for video/audio. Only ever rises."},"answerRef":{"type":"string","description":"`form_submission/<sk>` — where the answer was saved. Set by the server."},"review":{"type":"object","description":"Set when the item needs a person to approve it.","properties":{"status":{"type":"string","enum":["pending","approved","changes"]},"by":{"type":"string","description":"Reviewer email"},"at":{"type":"string","format":"date-time"},"note":{"type":"string"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such enrollment. — `id` names no post_progress.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such enrollment.","path":"/content-studio/enrollments/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"One enrollment in full","description":"A person’s post_progress with the course view worked out from it, the owner and the raw `progress` map.\n\n#### Signature\n\n```http\nGET /content-studio/enrollments/{id} (id: string) -> The course view plus { person, progress }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | No such enrollment. | `id` names no post_progress. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/content-studio/enrollments/{id}/review/{itemId}":{"post":{"operationId":"ContentStudioController_review","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"post_progress sk","example":"66f2b1c0ffee"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Outline (toc) item id — a page id also works for the item that shows it.","example":"p-quiz-1"}],"responses":{"201":{"description":"The course view","content":{"application/json":{"schema":{"type":"object","properties":{"course":{"type":"object","properties":{"id":{"type":"string","description":"post sk"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"coverImage":{"type":"object","additionalProperties":true},"dueInDays":{"type":"number"},"layout":{"type":"string","enum":["sidebar","steps"]},"navigation":{"type":"string","enum":["sidebar","top","none"]},"steps":{"type":"integer","description":"Number of pages"}}},"enrollment":{"type":"object","nullable":true,"description":"The caller’s post_progress, or null when not enrolled.","properties":{"id":{"type":"string","description":"post_progress sk"},"status":{"type":"string","enum":["active","completed","withdrawn"]},"enrolledAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"percent":{"type":"number"}}},"outline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Tree, as the sidebar shows it"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Flat, in reading order"},"next":{"type":"string","nullable":true,"description":"The first open page not yet done"}}}}}},"400":{"description":"Decision must be approved or changes. — `status` is anything else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Decision must be approved or changes.","path":"/content-studio/enrollments/{id}/review/{itemId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You are not a reviewer for this item. — The caller is not an org admin and not in the page’s `reviewers`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You are not a reviewer for this item.","path":"/content-studio/enrollments/{id}/review/{itemId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No such enrollment. — `id` names no post_progress.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such enrollment.","path":"/content-studio/enrollments/{id}/review/{itemId}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"Decide on an item","description":"A reviewer’s decision, written straight onto the post_progress: `approved` marks the item done; `changes` sends it back (not done) with the note, and the person can send it in again. Allowed for org admins and for the page’s `reviewers` (by email or group). When the approval finishes the course, the post_progress turns completed and `post_progress.saved` is raised.\n\n#### Signature\n\n```http\nPOST /content-studio/enrollments/{id}/review/{itemId} (id: string, itemId: string, body) -> The course view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Decision must be approved or changes. | `status` is anything else. | — |\n| `404` | — | No such enrollment. | `id` names no post_progress. | — |\n| `403` | — | You are not a reviewer for this item. | The caller is not an org admin and not in the page’s `reviewers`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["approved","changes"]},"note":{"type":"string"}}},"examples":{"approve":{"summary":"Approve","value":{"status":"approved"}},"changes":{"summary":"Ask for changes","value":{"status":"changes","note":"Add the storage temperatures for dairy."}}}}}}}},"/content-studio/enrollments/{id}/withdraw":{"post":{"operationId":"ContentStudioController_withdraw","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"post_progress sk","example":"66f2b1c0ffee"}],"responses":{"201":{"description":"{ ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such enrollment. — `id` names no post_progress.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such enrollment.","path":"/content-studio/enrollments/{id}/withdraw","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"Withdraw an enrollment","description":"Sets the post_progress to withdrawn. Its progress is kept; it no longer shows in the person’s courses or the review queue, and enrolling again creates a new one.\n\n#### Signature\n\n```http\nPOST /content-studio/enrollments/{id}/withdraw (id: string) -> { ok: true }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | No such enrollment. | `id` names no post_progress. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/content-studio/{course}/preview":{"get":{"operationId":"ContentStudioController_preview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"}],"responses":{"200":{"description":"The course view with `preview: true`, `first` and `pages`","content":{"application/json":{"schema":{"type":"object","properties":{"course":{"type":"object","properties":{"id":{"type":"string","description":"post sk"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"coverImage":{"type":"object","additionalProperties":true},"dueInDays":{"type":"number"},"layout":{"type":"string","enum":["sidebar","steps"]},"navigation":{"type":"string","enum":["sidebar","top","none"]},"steps":{"type":"integer","description":"Number of pages"}}},"enrollment":{"type":"object","nullable":true,"description":"The caller’s post_progress, or null when not enrolled.","properties":{"id":{"type":"string","description":"post_progress sk"},"status":{"type":"string","enum":["active","completed","withdrawn"]},"enrolledAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"percent":{"type":"number"}}},"outline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Tree, as the sidebar shows it"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Flat, in reading order"},"next":{"type":"string","nullable":true,"description":"The first open page not yet done"},"preview":{"type":"boolean"},"first":{"type":"string","nullable":true},"pages":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/content-studio/{course}/preview","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"Preview a course","description":"The post as its author checks it before anyone takes it: every page open, nothing recorded, read from the working draft. Items that would be locked for a learner carry `opensWhen` instead, so conditions can be checked without enrolling a test account. `first` is where Start goes; `pages` holds every page’s content by id.\n\n#### Signature\n\n```http\nGET /content-studio/{course}/preview (course: string) -> The course view with `preview: true`, `first` and `pages`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/content-studio/{course}/preview-key":{"post":{"operationId":"ContentStudioController_previewKey","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"course","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Courses"]}},"/content-studio/{course}/enrollments":{"post":{"operationId":"ContentStudioController_enrollPeople","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"}],"responses":{"201":{"description":"Array of { email, enrollmentId, already? }","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"enrollmentId":{"type":"string"},"already":{"type":"boolean"}}}},"example":[{"email":"ana@example.com","enrollmentId":"66f2b1c0ffee"},{"email":"ben@example.com","enrollmentId":"66f2b1c0ffef","already":true}]}}},"400":{"description":"Give at least one email address. — No valid email in `emails`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give at least one email address.","path":"/content-studio/{course}/enrollments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/content-studio/{course}/enrollments","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"Enroll people by email","description":"Creates a post_progress per email, owned by the user or customer with that email — or by the email alone, matched when they sign in. People already enrolled are reported with `already: true` and left as they are. Invalid emails are skipped.\n\n#### Signature\n\n```http\nPOST /content-studio/{course}/enrollments (course: string, body) -> Array of { email, enrollmentId, already? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Give at least one email address. | No valid email in `emails`. | — |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emails":{"type":"array","items":{"type":"string"}}}},"example":{"emails":["ana@example.com","ben@example.com"]}}}}},"get":{"operationId":"ContentStudioController_roster","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["active","completed","withdrawn"]}},{"name":"search","required":false,"in":"query","schema":{"type":"string"},"description":"Matches the person’s email or name."},{"name":"page","required":false,"in":"query","schema":{"type":"integer","default":1}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer","default":50},"description":"At most 200."}],"responses":{"200":{"description":"{ data: [{ id, person, status, percent, next, enrolledAt, dueAt, completedAt, waitingReview: itemId[] }], total }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"total":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/content-studio/{course}/enrollments","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"Course roster","description":"Everyone enrolled in the post, most recently active first: status, % complete, next item, dates, and the items waiting for review.\n\n#### Signature\n\n```http\nGET /content-studio/{course}/enrollments (course: string, status?: string, search?: string, page?: integer, pageSize?: integer) -> { data: [{ id, person, status, percent, next, enrolledAt, dueAt, completedAt, waitingReview: itemId[] }], total }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/content-studio/{course}/review":{"get":{"operationId":"ContentStudioController_reviewCourse","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"}],"responses":{"200":{"description":"Review rows","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"enrollmentId":{"type":"string","description":"post_progress sk"},"postId":{"type":"string"},"itemId":{"type":"string"},"person":{"type":"object","description":"The post_progress owner","properties":{"datatype":{"type":"string"},"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"}}},"answerRef":{"type":"string"},"answer":{"type":"object","nullable":true,"additionalProperties":true,"description":"The values sent in, so the work itself is judged"},"submittedAt":{"type":"string","format":"date-time"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/content-studio/{course}/review","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Staff"],"summary":"Waiting for review — one course","description":"Items in this post waiting for a reviewer, oldest first, each with the answer that was sent in. Only active post_progress records are looked at.\n\n#### Signature\n\n```http\nGET /content-studio/{course}/review (course: string) -> Review rows\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Owner`, `ConfigAdmin`, `ContentAdmin`, `User (signed-in staff)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /content-studio/enrollments/{id}/review/{itemId}`"}},"/client/content-studio/mine":{"get":{"operationId":"ContentStudioClientController_mine","summary":"My courses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Array of { enrollmentId, course: { id, slug, title, coverImage }, status, percent, next, dueAt }","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"enrollmentId":{"type":"string"},"course":{"type":"object","additionalProperties":true},"status":{"type":"string","enum":["active","completed"]},"percent":{"type":"number"},"next":{"type":"string","nullable":true},"dueAt":{"type":"string"}}}},"example":[{"enrollmentId":"66f2b1c0ffee","course":{"id":"66f2a0c0ffee","slug":"allergen-awareness","title":"Allergen awareness"},"status":"active","percent":33,"next":"p-quiz-1","dueAt":"2026-10-04T15:02:11.000Z"}]}}},"401":{"description":"Sign in to continue. — No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to continue.","path":"/client/content-studio/mine","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Participant"],"description":"The caller’s courses, applications and trainings — every post_progress they own that is not withdrawn, most recently touched first (up to 200) — with status, % complete and the next item. A post taken down since is left out.\n\n#### Signature\n\n```http\nGET /client/content-studio/mine () -> Array of { enrollmentId, course: { id, slug, title, coverImage }, status, percent, next, dueAt }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGNIN_REQUIRED | Sign in to continue. | No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`. | — |\n\nPlus the standard platform errors: `403`, `429`, `500`."}},"/client/content-studio/{course}":{"get":{"operationId":"ContentStudioClientController_outline","summary":"A course, its outline and my enrollment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"},{"name":"code","required":false,"in":"query","schema":{"type":"string"},"description":"Access code, when the post’s access mode asks for one.","example":"SPRING26"}],"responses":{"200":{"description":"The course view","content":{"application/json":{"schema":{"type":"object","properties":{"course":{"type":"object","properties":{"id":{"type":"string","description":"post sk"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"coverImage":{"type":"object","additionalProperties":true},"dueInDays":{"type":"number"},"layout":{"type":"string","enum":["sidebar","steps"]},"navigation":{"type":"string","enum":["sidebar","top","none"]},"steps":{"type":"integer","description":"Number of pages"}}},"enrollment":{"type":"object","nullable":true,"description":"The caller’s post_progress, or null when not enrolled.","properties":{"id":{"type":"string","description":"post_progress sk"},"status":{"type":"string","enum":["active","completed","withdrawn"]},"enrolledAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"percent":{"type":"number"}}},"outline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Tree, as the sidebar shows it"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Flat, in reading order"},"next":{"type":"string","nullable":true,"description":"The first open page not yet done"}}},"example":{"course":{"id":"66f2a0c0ffee","slug":"allergen-awareness","title":"Allergen awareness","layout":"sidebar","navigation":"sidebar","steps":3,"dueInDays":14},"enrollment":{"id":"66f2b1c0ffee","status":"active","enrolledAt":"2026-09-20T15:02:11.000Z","dueAt":"2026-10-04T15:02:11.000Z","percent":33},"outline":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","type":"video","status":"done","percent":100}],"items":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","status":"done","percent":100,"step":1},{"id":"p-quiz-1","title":"Check your knowledge","pageId":"p-quiz-1","depth":0,"kind":"page","type":"quiz","status":"open","percent":0,"step":2},{"id":"p-plan","title":"Your allergen plan","pageId":"p-plan","depth":0,"kind":"page","type":"form","status":"locked","lockReason":"Opens when “Check your knowledge” scores 80 or more","percent":0,"step":3}],"next":"p-quiz-1"}}}},"401":{"description":"This course needs an access code. — The post’s access mode needs a code (or an invitation link: “This course is by invitation. Open it from the link you were sent.”) and none was sent. `reason: \"code-required\"`, `stage: \"access\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"This course needs an access code.","path":"/client/content-studio/{course}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This course is not open yet. — Before the post’s start date, after its end date (“This course has closed.”), or its status has ended it. Body: `{ message, reason: \"not-open-yet\" | \"closed\", stage: \"open\", title, accessMode, authenticationType }`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This course is not open yet.","path":"/client/content-studio/{course}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/client/content-studio/{course}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Participant"],"description":"The post as its player shows it: layout, every outline item with its status and — when locked — why, % complete, the next item, and the caller’s post_progress summary (null when not enrolled). Readable before signing in, so a course page can sell itself; the post’s own access rules (open dates, access code, required identity) apply.\n\n#### Signature\n\n```http\nGET /client/content-studio/{course} (course: string, code?: string) -> The course view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n| `403` | NOT_OPEN | This course is not open yet. | Before the post’s start date, after its end date (“This course has closed.”), or its status has ended it. Body: `{ message, reason: \"not-open-yet\" \\| \"closed\", stage: \"open\", title, accessMode, authenticationType }`. | — |\n| `401` | CODE_REQUIRED | This course needs an access code. | The post’s access mode needs a code (or an invitation link: “This course is by invitation. Open it from the link you were sent.”) and none was sent. `reason: \"code-required\"`, `stage: \"access\"`. | Ask for the code and send it as `?code=`. |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/content-studio/{course}/preview":{"get":{"operationId":"ContentStudioClientController_preview","summary":"The author’s preview: every page open, nothing recorded — for whoever holds a live preview key","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"course","required":true,"in":"path","schema":{"type":"string"}},{"name":"key","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Content Studio Client"]}},"/client/content-studio/{course}/enroll":{"post":{"operationId":"ContentStudioClientController_enroll","summary":"Enroll myself","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"},{"name":"code","required":false,"in":"query","schema":{"type":"string"},"description":"Access code, when the post’s access mode asks for one.","example":"SPRING26"}],"responses":{"201":{"description":"The course view, with `enrollment` set","content":{"application/json":{"schema":{"type":"object","properties":{"course":{"type":"object","properties":{"id":{"type":"string","description":"post sk"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"coverImage":{"type":"object","additionalProperties":true},"dueInDays":{"type":"number"},"layout":{"type":"string","enum":["sidebar","steps"]},"navigation":{"type":"string","enum":["sidebar","top","none"]},"steps":{"type":"integer","description":"Number of pages"}}},"enrollment":{"type":"object","nullable":true,"description":"The caller’s post_progress, or null when not enrolled.","properties":{"id":{"type":"string","description":"post_progress sk"},"status":{"type":"string","enum":["active","completed","withdrawn"]},"enrolledAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"percent":{"type":"number"}}},"outline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Tree, as the sidebar shows it"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Flat, in reading order"},"next":{"type":"string","nullable":true,"description":"The first open page not yet done"}}},"example":{"course":{"id":"66f2a0c0ffee","slug":"allergen-awareness","title":"Allergen awareness","layout":"sidebar","navigation":"sidebar","steps":3,"dueInDays":14},"enrollment":{"id":"66f2b1c0ffee","status":"active","enrolledAt":"2026-09-20T15:02:11.000Z","dueAt":"2026-10-04T15:02:11.000Z","percent":33},"outline":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","type":"video","status":"done","percent":100}],"items":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","status":"done","percent":100,"step":1},{"id":"p-quiz-1","title":"Check your knowledge","pageId":"p-quiz-1","depth":0,"kind":"page","type":"quiz","status":"open","percent":0,"step":2},{"id":"p-plan","title":"Your allergen plan","pageId":"p-plan","depth":0,"kind":"page","type":"form","status":"locked","lockReason":"Opens when “Check your knowledge” scores 80 or more","percent":0,"step":3}],"next":"p-quiz-1"}}}},"401":{"description":"Sign in to continue. — No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to continue.","path":"/client/content-studio/{course}/enroll","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"This course is not open yet. — Before the post’s start date, after its end date (“This course has closed.”), or its status has ended it. Body: `{ message, reason: \"not-open-yet\" | \"closed\", stage: \"open\", title, accessMode, authenticationType }`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This course is not open yet.","path":"/client/content-studio/{course}/enroll","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/client/content-studio/{course}/enroll","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The enrollment could not be saved. — The post_progress could not be written.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The enrollment could not be saved.","path":"/client/content-studio/{course}/enroll","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Content Studio · Participant"],"description":"Creates the caller’s post_progress for the post (status active, `dueAt` from the post’s `course.dueInDays`), or returns the one they already have — calling it twice does not enroll twice. The post’s access rules apply.\n\n#### Signature\n\n```http\nPOST /client/content-studio/{course}/enroll (course: string, code?: string) -> The course view, with `enrollment` set\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGNIN_REQUIRED | Sign in to continue. | No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`. | — |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n| `403` | NOT_OPEN | This course is not open yet. | Before the post’s start date, after its end date (“This course has closed.”), or its status has ended it. Body: `{ message, reason: \"not-open-yet\" \\| \"closed\", stage: \"open\", title, accessMode, authenticationType }`. | — |\n| `500` | — | The enrollment could not be saved. | The post_progress could not be written. | Retry. |\n\nPlus the standard platform errors: `429`."}},"/client/content-studio/{course}/items/{itemId}":{"get":{"operationId":"ContentStudioClientController_item","summary":"Open an item","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Outline (toc) item id — a page id also works for the item that shows it.","example":"p-quiz-1"},{"name":"code","required":false,"in":"query","schema":{"type":"string"},"description":"Access code, when the post’s access mode asks for one.","example":"SPRING26"}],"responses":{"200":{"description":"{ item: { id, title, prev, next }, page: { id, title, summary, content, type, duration, reviewed } | null, progress, answer, enrolled }","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"object","additionalProperties":true},"page":{"type":"object","nullable":true,"additionalProperties":true},"progress":{"type":"object","properties":{"done":{"type":"boolean"},"at":{"type":"string","format":"date-time"},"score":{"type":"number","description":"0–100. For pages with an answer key, only the server sets it."},"percent":{"type":"number","description":"Watched % for video/audio. Only ever rises."},"answerRef":{"type":"string","description":"`form_submission/<sk>` — where the answer was saved. Set by the server."},"review":{"type":"object","description":"Set when the item needs a person to approve it.","properties":{"status":{"type":"string","enum":["pending","approved","changes"]},"by":{"type":"string","description":"Reviewer email"},"at":{"type":"string","format":"date-time"},"note":{"type":"string"}}}}},"answer":{"type":"object","nullable":true,"additionalProperties":true},"enrolled":{"type":"boolean"}}}}}},"401":{"description":"This course needs an access code. — The post’s access mode needs a code (or an invitation link: “This course is by invitation. Open it from the link you were sent.”) and none was sent. `reason: \"code-required\"`, `stage: \"access\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"This course needs an access code.","path":"/client/content-studio/{course}/items/{itemId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"<why it is locked> — The item’s conditions do not hold yet. The body carries `reason: \"locked\"` and the lock reason as `message`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"<why it is locked>","path":"/client/content-studio/{course}/items/{itemId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/client/content-studio/{course}/items/{itemId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Content Studio · Participant"],"description":"One item’s page content, when it is open. Reading needs no enrollment — a first lesson opens for anyone the post’s access rules let in, with locks worked out on no progress. When the caller is enrolled, `progress` and `answer` are theirs, so a step can be resumed or corrected. `page.reviewed` says the page waits for a person after it is sent in. The page’s answer key is never returned.\n\n#### Signature\n\n```http\nGET /client/content-studio/{course}/items/{itemId} (course: string, itemId: string, code?: string) -> { item: { id, title, prev, next }, page: { id, title, summary, content, type, duration, reviewed } \\| null, progress, answer, enrolled }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n| `403` | LOCKED | <why it is locked> | The item’s conditions do not hold yet. The body carries `reason: \"locked\"` and the lock reason as `message`. | Finish what the message names first. |\n| `401` | CODE_REQUIRED | This course needs an access code. | The post’s access mode needs a code (or an invitation link: “This course is by invitation. Open it from the link you were sent.”) and none was sent. `reason: \"code-required\"`, `stage: \"access\"`. | Ask for the code and send it as `?code=`. |\n\nPlus the standard platform errors: `429`, `500`."}},"/client/content-studio/{course}/items/{itemId}/progress":{"post":{"operationId":"ContentStudioClientController_progress","summary":"Report an item","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Outline (toc) item id — a page id also works for the item that shows it.","example":"p-quiz-1"},{"name":"code","required":false,"in":"query","schema":{"type":"string"},"description":"Access code, when the post’s access mode asks for one.","example":"SPRING26"}],"responses":{"201":{"description":"The course view","content":{"application/json":{"schema":{"type":"object","properties":{"course":{"type":"object","properties":{"id":{"type":"string","description":"post sk"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"coverImage":{"type":"object","additionalProperties":true},"dueInDays":{"type":"number"},"layout":{"type":"string","enum":["sidebar","steps"]},"navigation":{"type":"string","enum":["sidebar","top","none"]},"steps":{"type":"integer","description":"Number of pages"}}},"enrollment":{"type":"object","nullable":true,"description":"The caller’s post_progress, or null when not enrolled.","properties":{"id":{"type":"string","description":"post_progress sk"},"status":{"type":"string","enum":["active","completed","withdrawn"]},"enrolledAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"percent":{"type":"number"}}},"outline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Tree, as the sidebar shows it"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Flat, in reading order"},"next":{"type":"string","nullable":true,"description":"The first open page not yet done"}}},"example":{"course":{"id":"66f2a0c0ffee","slug":"allergen-awareness","title":"Allergen awareness","layout":"sidebar","navigation":"sidebar","steps":3,"dueInDays":14},"enrollment":{"id":"66f2b1c0ffee","status":"active","enrolledAt":"2026-09-20T15:02:11.000Z","dueAt":"2026-10-04T15:02:11.000Z","percent":33},"outline":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","type":"video","status":"done","percent":100}],"items":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","status":"done","percent":100,"step":1},{"id":"p-quiz-1","title":"Check your knowledge","pageId":"p-quiz-1","depth":0,"kind":"page","type":"quiz","status":"open","percent":0,"step":2},{"id":"p-plan","title":"Your allergen plan","pageId":"p-plan","depth":0,"kind":"page","type":"form","status":"locked","lockReason":"Opens when “Check your knowledge” scores 80 or more","percent":0,"step":3}],"next":"p-quiz-1"}}}},"401":{"description":"Sign in to continue. — No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to continue.","path":"/client/content-studio/{course}/items/{itemId}/progress","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Enroll in this course first. — The caller has no active post_progress for this post.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Enroll in this course first.","path":"/client/content-studio/{course}/items/{itemId}/progress","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/client/content-studio/{course}/items/{itemId}/progress","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Progress could not be saved. — The post_progress could not be written.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"Progress could not be saved.","path":"/client/content-studio/{course}/items/{itemId}/progress","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Content Studio · Participant"],"description":"What happened on a page with nothing to send in: `done`, how much was watched (`percent`, 0–100, only ever rises) or a `score`. A score is ignored on a page with an answer key — only the server marks those (see /answer). `done` on a page with reviewers does not finish it: the item goes to `review.status = pending`. An item already approved is left as it is. When every page is done the post_progress turns `completed` with `completedAt`, and a `post_progress.saved` event is raised (Business Made readiness listens to it).\n\n#### Signature\n\n```http\nPOST /client/content-studio/{course}/items/{itemId}/progress (course: string, itemId: string, code?: string, body) -> The course view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | SIGNIN_REQUIRED | Sign in to continue. | No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`. | — |\n| `403` | — | Enroll in this course first. | The caller has no active post_progress for this post. | POST /client/content-studio/{course}/enroll |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n| `500` | — | Progress could not be saved. | The post_progress could not be written. | Retry. |\n\nPlus the standard platform errors: `429`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"done":{"type":"boolean"},"percent":{"type":"number","minimum":0,"maximum":100},"score":{"type":"number"}}},"examples":{"watched":{"summary":"Video progress","value":{"percent":72}},"done":{"summary":"Page finished","value":{"done":true}}}}}}}},"/client/content-studio/{course}/items/{itemId}/answer":{"post":{"operationId":"ContentStudioClientController_answer","summary":"Send in an item’s answer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"course","required":true,"in":"path","schema":{"type":"string"},"description":"The post’s sk or slug.","example":"allergen-awareness"},{"name":"itemId","required":true,"in":"path","schema":{"type":"string"},"description":"Outline (toc) item id — a page id also works for the item that shows it.","example":"p-quiz-1"},{"name":"code","required":false,"in":"query","schema":{"type":"string"},"description":"Access code, when the post’s access mode asks for one.","example":"SPRING26"}],"responses":{"201":{"description":"The course view","content":{"application/json":{"schema":{"type":"object","properties":{"course":{"type":"object","properties":{"id":{"type":"string","description":"post sk"},"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"coverImage":{"type":"object","additionalProperties":true},"dueInDays":{"type":"number"},"layout":{"type":"string","enum":["sidebar","steps"]},"navigation":{"type":"string","enum":["sidebar","top","none"]},"steps":{"type":"integer","description":"Number of pages"}}},"enrollment":{"type":"object","nullable":true,"description":"The caller’s post_progress, or null when not enrolled.","properties":{"id":{"type":"string","description":"post_progress sk"},"status":{"type":"string","enum":["active","completed","withdrawn"]},"enrolledAt":{"type":"string","format":"date-time"},"dueAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"percent":{"type":"number"}}},"outline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Tree, as the sidebar shows it"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"pageId":{"type":"string"},"depth":{"type":"integer"},"kind":{"type":"string","enum":["chapter","page"]},"type":{"type":"string","description":"The page’s own type as the author set it (video, pdf, quiz, form, text…)"},"duration":{"type":"string"},"status":{"type":"string","enum":["locked","open","pending-review","changes","done"]},"lockReason":{"type":"string","description":"Why it is locked, in words the page can show"},"percent":{"type":"number"},"done":{"type":"integer","description":"Chapters: pages done"},"total":{"type":"integer","description":"Chapters: pages in it"},"step":{"type":"integer","description":"Flat list only: position among pages"},"children":{"type":"array","items":{"type":"object"}}}},"description":"Flat, in reading order"},"next":{"type":"string","nullable":true,"description":"The first open page not yet done"}}},"example":{"course":{"id":"66f2a0c0ffee","slug":"allergen-awareness","title":"Allergen awareness","layout":"sidebar","navigation":"sidebar","steps":3,"dueInDays":14},"enrollment":{"id":"66f2b1c0ffee","status":"active","enrolledAt":"2026-09-20T15:02:11.000Z","dueAt":"2026-10-04T15:02:11.000Z","percent":33},"outline":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","type":"video","status":"done","percent":100}],"items":[{"id":"p-intro","title":"Introduction","pageId":"p-intro","depth":0,"kind":"page","status":"done","percent":100,"step":1},{"id":"p-quiz-1","title":"Check your knowledge","pageId":"p-quiz-1","depth":0,"kind":"page","type":"quiz","status":"open","percent":0,"step":2},{"id":"p-plan","title":"Your allergen plan","pageId":"p-plan","depth":0,"kind":"page","type":"form","status":"locked","lockReason":"Opens when “Check your knowledge” scores 80 or more","percent":0,"step":3}],"next":"p-quiz-1"}}}},"400":{"description":"Nothing to save. — `values` is missing or not an object.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Nothing to save.","path":"/client/content-studio/{course}/items/{itemId}/answer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Sign in to continue. — No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to continue.","path":"/client/content-studio/{course}/items/{itemId}/answer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Enroll in this course first. — The caller has no active post_progress for this post.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Enroll in this course first.","path":"/client/content-studio/{course}/items/{itemId}/answer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No post by that name. — `course` matches no post’s sk or slug (for participants: no published copy).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No post by that name.","path":"/client/content-studio/{course}/items/{itemId}/answer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This was approved and can no longer be changed. — A reviewer already approved this item.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This was approved and can no longer be changed.","path":"/client/content-studio/{course}/items/{itemId}/answer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"413":{"description":"That answer is too large. Upload files instead of pasting them. — `values` is over about 200 KB as JSON.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":413,"error":"That answer is too large. Upload files instead of pasting them.","path":"/client/content-studio/{course}/items/{itemId}/answer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The answer could not be saved. — The form_submission could not be written.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The answer could not be saved.","path":"/client/content-studio/{course}/items/{itemId}/answer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Content Studio · Participant"],"description":"Saves the page’s form values to one form_submission per item (owned by the caller’s post_progress, written again on each save) and reports the item. `done: false` keeps a draft without finishing the step. When finished, questions with a right answer are marked by the server against the page’s answer key — the share of keyed questions answered exactly right, 0–100 — and that becomes the item’s `score`. A page with reviewers then waits for review instead of being done.\n\n#### Signature\n\n```http\nPOST /client/content-studio/{course}/items/{itemId}/answer (course: string, itemId: string, code?: string, body) -> The course view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | — | Nothing to save. | `values` is missing or not an object. | — |\n| `413` | — | That answer is too large. Upload files instead of pasting them. | `values` is over about 200 KB as JSON. | Upload files and send their references. |\n| `409` | — | This was approved and can no longer be changed. | A reviewer already approved this item. | — |\n| `401` | SIGNIN_REQUIRED | Sign in to continue. | No signed-in person (the site’s app token alone is nobody). Body carries `reason: \"signin-required\"`. | — |\n| `403` | — | Enroll in this course first. | The caller has no active post_progress for this post. | POST /client/content-studio/{course}/enroll |\n| `404` | — | No post by that name. | `course` matches no post’s sk or slug (for participants: no published copy). | — |\n| `500` | — | The answer could not be saved. | The form_submission could not be written. | Retry. |\n\nPlus the standard platform errors: `429`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"values":{"type":"object","additionalProperties":true,"description":"Field name → value. Multiple choice: the option values."},"done":{"type":"boolean","default":true}}},"examples":{"quiz":{"summary":"Quiz answers","value":{"values":{"q1":"sesame","q2":["peanuts","tree-nuts"]}}},"draft":{"summary":"Save a draft","value":{"values":{"businessName":"Sunrise Café"},"done":false}}}}}}}},"/staff-directory":{"get":{"operationId":"StaffDirectoryController_list","summary":"Everyone who works here","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"search","required":false,"in":"query","schema":{"type":"string"},"example":"kitchen"},{"name":"department","required":false,"in":"query","schema":{"type":"string"},"example":"Kitchen"},{"name":"kind","required":false,"in":"query","schema":{"type":"string","enum":["people","ai"]},"example":"people"},{"name":"page","required":false,"in":"query","schema":{"type":"integer"},"example":1},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"},"description":"Default 100, at most 500.","example":100}],"responses":{"200":{"description":"`{ data:[{id, email, name, title, department, location, phone, avatar, roles, supervisor, employeeId, status, ai}], total, page, pageSize, departments }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"The staff directory is for staff. — The caller is not a signed-in staff user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"The staff directory is for staff.","path":"/staff-directory","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff directory"],"description":"People and AI employees: name, email, title, department, location, company phone, avatar, roles, supervisor, employee id, status and whether it is an AI. `search` matches name, email, title, department, location or role; `department` filters exactly; `kind=people` or `kind=ai`. Also returns the departments present. Signed-in staff only — customers and site visitors have no directory.\n\n#### Signature\n\n```http\nGET /staff-directory (search?: string, department?: string, kind?: string, page?: integer, pageSize?: integer) -> `{ data:[{id, email, name, title, department, location, phone, avatar, roles, supervisor, employeeId, status, ai}], total, page, pageSize, departments }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | STAFF_ONLY | The staff directory is for staff. | The caller is not a signed-in staff user. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/staff-directory/{ref}":{"get":{"operationId":"StaffDirectoryController_one","summary":"One colleague","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"ref","required":true,"in":"path","schema":{"type":"string"},"description":"Email or user id.","example":"ana@example.com"}],"responses":{"200":{"description":"The directory row","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"The staff directory is for staff. — The caller is not a signed-in staff user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"The staff directory is for staff.","path":"/staff-directory/{ref}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No one by that name works here. — Nobody has that email or id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No one by that name works here.","path":"/staff-directory/{ref}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff directory"],"description":"One person or AI employee, by email or user id.\n\n#### Signature\n\n```http\nGET /staff-directory/{ref} (ref: string) -> The directory row\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | STAFF_ONLY | The staff directory is for staff. | The caller is not a signed-in staff user. | — |\n| `404` | NOT_FOUND | No one by that name works here. | Nobody has that email or id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/staff-portal/dashboard":{"get":{"operationId":"StaffPortalController_dashboard","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The dashboard","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my dashboard","description":"The employee's home view — next shift, current timesheet, latest payslip and anything needing attention, in one call.\n\n#### Signature\n\n```http\nGET /staff-portal/dashboard () -> The dashboard\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/schedule/upcoming`"}},"/staff-portal/landing":{"get":{"operationId":"StaffPortalController_landing","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ isAdmin, isStaff, landing, staffOnly, preboarding, managesTeam, … }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Where do I belong","description":"Decided on the server so web, mobile and IVR agree: `isAdmin` (owner / config admin roles), `isStaff` (linked to an employee record), `landing` (`/staff-portal/onboarding` for a hire in pre-boarding, `/staff-portal/dashboard` for staff only, `/dashboard` for admins), `staffOnly`, `preboarding`, `managesTeam` (has direct reports), and any back-office screens an admin gave this person's roles. Also returns the Leave module's readiness.\n\n#### Signature\n\n```http\nGET /staff-portal/landing () -> `{ isAdmin, isStaff, landing, staffOnly, preboarding, managesTeam, … }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/time-off":{"get":{"operationId":"StaffPortalController_myTimeOff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ employeeId, state, types, balances, requests, approvers, year }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"My time off","description":"The leave types I can request, my balances this year, my requests, and who decides them. While the Leave module is not ready, `state` says so and the lists are empty.\n\n#### Signature\n\n```http\nGET /staff-portal/time-off () -> `{ employeeId, state, types, balances, requests, approvers, year }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /staff-portal/time-off`"},"post":{"operationId":"StaffPortalController_requestTimeOff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The request (pending, or draft)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Pick a type of time off — `leaveTypeId` names no leave type.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pick a type of time off","path":"/staff-portal/time-off","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"Time off is not switched on for this business — The Leave module is off, or still being set up (\"Time off is still being set up\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Time off is not switched on for this business","path":"/staff-portal/time-off","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Request time off","description":"Creates a request as me — the employee on it is always the caller — and submits it for approval, unless `draft: true`. Hours are worked out from my weekly hours. Submission follows the same rules as HR's submit (working days only, approver resolved, balance held).\n\n#### Signature\n\n```http\nPOST /staff-portal/time-off (body) -> The request (pending, or draft)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `412` | LEAVE_NOT_READY | Time off is not switched on for this business | The Leave module is off, or still being set up (\"Time off is still being set up\"). | — |\n| `400` | LEAVE_TYPE_REQUIRED | Pick a type of time off | `leaveTypeId` names no leave type. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/time-off/cost`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["leaveTypeId","startDate"],"properties":{"leaveTypeId":{"type":"string"},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"isPartialDay":{"type":"boolean"},"reason":{"type":"string"},"draft":{"type":"boolean"}}},"example":{"leaveTypeId":"LT-annual","startDate":"2026-10-05","endDate":"2026-10-09","reason":"Family visit"}}}}}},"/staff-portal/time-off/cost":{"get":{"operationId":"StaffPortalController_costTimeOff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"},"example":"2026-10-05"},{"name":"endDate","required":false,"in":"query","schema":{"type":"string"},"example":"2026-10-09"},{"name":"isPartialDay","required":false,"in":"query","schema":{"type":"string"},"example":"false"}],"responses":{"200":{"description":"`{ unit, amount, calendarDays, workingDays, skipped, totalDays, totalHours }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"What a date range would cost me","description":"The amount a request would take from my balance — working days only, holidays and closures skipped — before I send it.\n\n#### Signature\n\n```http\nGET /staff-portal/time-off/cost (startDate?: string, endDate?: string, isPartialDay?: string) -> `{ unit, amount, calendarDays, workingDays, skipped, totalDays, totalHours }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/time-off/{id}/edit":{"post":{"operationId":"StaffPortalController_editTimeOff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_leave_request sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"The request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"A pending request cannot be edited — cancel it first — The request is pending or approved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A pending request cannot be edited — cancel it first","path":"/staff-portal/time-off/{id}/edit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"That is not your request — The request belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That is not your request","path":"/staff-portal/time-off/{id}/edit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Edit my request","description":"Changes one of my own draft, declined or cancelled requests (dates, type, reason, half day).\n\n#### Signature\n\n```http\nPOST /staff-portal/time-off/{id}/edit (id: string, body) -> The request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | NOT_YOUR_REQUEST | That is not your request | The request belongs to someone else. | — |\n| `400` | BAD_STATUS | A pending request cannot be edited — cancel it first | The request is pending or approved. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string"},"endDate":{"type":"string"},"leaveTypeId":{"type":"string"},"reason":{"type":"string"},"isPartialDay":{"type":"boolean"}}},"example":{"endDate":"2026-10-08"}}}}}},"/staff-portal/time-off/{id}/resubmit":{"post":{"operationId":"StaffPortalController_resubmitTimeOff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_leave_request sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"The request, pending again","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a declined request, or one withdrawn before a decision, can be sent again — make a new request instead — The request is pending or approved.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a declined request, or one withdrawn before a decision, can be sent again — make a new request instead","path":"/staff-portal/time-off/{id}/resubmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"That is not your request — The request belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That is not your request","path":"/staff-portal/time-off/{id}/resubmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"412":{"description":"Time off is still being set up — The Leave module is not ready.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Time off is still being set up","path":"/staff-portal/time-off/{id}/resubmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Send my request again","description":"Sends one of my declined requests (or one withdrawn before a decision) back for approval, with optional changes. It goes back to whoever declined it.\n\n#### Signature\n\n```http\nPOST /staff-portal/time-off/{id}/resubmit (id: string, body) -> The request, pending again\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `412` | LEAVE_NOT_READY | Time off is still being set up | The Leave module is not ready. | — |\n| `403` | NOT_YOUR_REQUEST | That is not your request | The request belongs to someone else. | — |\n| `400` | CANNOT_RESUBMIT | Only a declined request, or one withdrawn before a decision, can be sent again — make a new request instead | The request is pending or approved. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"startDate":"2026-10-12","endDate":"2026-10-16"}}}}}},"/staff-portal/schedule/off":{"get":{"operationId":"StaffPortalController_myOffDays","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"days","required":false,"in":"query","schema":{"type":"integer"},"example":30}],"responses":{"200":{"description":"`{ from, to, days }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Days I am off","description":"Days in the window when I am on approved leave or the business is shut — for my schedule page.\n\n#### Signature\n\n```http\nGET /staff-portal/schedule/off (days?: integer) -> `{ from, to, days }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/time-off/team":{"get":{"operationId":"StaffPortalController_teamOff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"example":"2026-10-01"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"example":"2026-10-31"}],"responses":{"200":{"description":"`{ from, to, days:[{date, off:[{name, type, title, half}]}], … }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Who at my site is off","description":"Approved time off at my site, by day — names and types only. Default window: 30 days from today.\n\n#### Signature\n\n```http\nGET /staff-portal/time-off/team (from?: string, to?: string) -> `{ from, to, days:[{date, off:[{name, type, title, half}]}], … }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/approvals":{"get":{"operationId":"StaffPortalController_myApprovals","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ count, requests }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Requests waiting on me","description":"Time-off requests waiting on me as a supervisor or site manager — or, for HR, everything pending.\n\n#### Signature\n\n```http\nGET /staff-portal/approvals () -> `{ count, requests }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/approvals/{id}/{decision}":{"post":{"operationId":"StaffPortalController_decideApproval","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_leave_request sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"},{"name":"decision","required":true,"in":"path","schema":{"type":"string","enum":["approve","reject"]},"example":"approve"}],"responses":{"201":{"description":"The request after the decision","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Decision must be approve or reject — `decision` is anything else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Decision must be approve or reject","path":"/staff-portal/approvals/{id}/{decision}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This request is not waiting on you — Someone else decides it, or it is not pending.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This request is not waiting on you","path":"/staff-portal/approvals/{id}/{decision}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Decide a request","description":"Approves or declines a request that is waiting on me (HR may decide anything pending). A decline needs a note — the person sees it.\n\n#### Signature\n\n```http\nPOST /staff-portal/approvals/{id}/{decision} (id: string, decision: string, body) -> The request after the decision\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | NOT_YOUR_DECISION | This request is not waiting on you | Someone else decides it, or it is not pending. | — |\n| `400` | BAD_DECISION | Decision must be approve or reject | `decision` is anything else. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"}}},"example":{"note":"Enjoy the break"}}}}}},"/staff-portal/time-off/{id}/cancel":{"post":{"operationId":"StaffPortalController_cancelTimeOff","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_leave_request sk.","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"The cancelled request","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This request is already cancelled — Already cancelled or declined.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This request is already cancelled","path":"/staff-portal/time-off/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"That is not your request — The request belongs to someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That is not your request","path":"/staff-portal/time-off/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Cancel my request","description":"Withdraws one of my own requests. A pending one releases its held amount; an approved one gives it back.\n\n#### Signature\n\n```http\nPOST /staff-portal/time-off/{id}/cancel (id: string, body) -> The cancelled request\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | NOT_YOUR_REQUEST | That is not your request | The request belongs to someone else. | — |\n| `400` | BAD_STATUS | This request is already cancelled | Already cancelled or declined. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}},"example":{"reason":"Plans changed"}}}}}},"/staff-portal/availability":{"get":{"operationId":"StaffPortalController_myAvailability","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ employeeId, weekly, exceptions, maxHoursPerWeek?, notes? }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"My availability","description":"What I said I can work: weekly pattern, exceptions, maximum hours a week, notes. Empty lists when nothing is recorded.\n\n#### Signature\n\n```http\nGET /staff-portal/availability () -> `{ employeeId, weekly, exceptions, maxHoursPerWeek?, notes? }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"StaffPortalController_setMyAvailability","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The saved availability record","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Set my availability","description":"Replaces my availability. It is a preference: a manager can still schedule me outside it, flagged.\n\n#### Signature\n\n```http\nPUT /staff-portal/availability (body) -> The saved availability record\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"weekly":{"type":"array","items":{"type":"object","additionalProperties":true}},"exceptions":{"type":"array","items":{"type":"object","additionalProperties":true}},"maxHoursPerWeek":{"type":"number","nullable":true},"notes":{"type":"string"}}},"example":{"weekly":[{"day":"sat","from":"10:00","to":"18:00"}],"maxHoursPerWeek":20}}}}}},"/staff-portal/profile":{"get":{"operationId":"StaffPortalController_getProfile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my profile","description":"The employee's own record — contact details and employment information.\n\n#### Signature\n\n```http\nGET /staff-portal/profile () -> The profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /staff-portal/profile`"},"put":{"operationId":"StaffPortalController_updateProfile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The updated profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Update my profile","description":"Updates the employee's own details. Employment terms — pay rate, role, status — are not editable here; those are HR-controlled.\n\n#### Signature\n\n```http\nPUT /staff-portal/profile (body) -> The updated profile\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Cannot change pay or role.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /staff-portal/tax-info`","requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"phone":"+15551234567","address":{"line1":"1 Market St","city":"San Francisco","state":"CA","postalCode":"94105"}}}}}}},"/staff-portal/pay-method":{"get":{"operationId":"StaffPortalController_getPayMethod","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The pay method","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my pay method","description":"How the employee is paid — ACH, check or cash — and the accounts on file.\n\n#### Signature\n\n```http\nGET /staff-portal/pay-method () -> The pay method\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /staff-portal/pay-method`"},"put":{"operationId":"StaffPortalController_setPayMethod","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"payMethod must be 'ach', 'check', or 'cash' — The value is missing or not one of the three.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"payMethod must be 'ach', 'check', or 'cash'","path":"/staff-portal/pay-method","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Set my pay method","description":"Chooses how the employee is paid. Choosing `ach` without a bank account on file leaves the next payroll run with nowhere to send the money — add the account first.\n\n#### Signature\n\n```http\nPUT /staff-portal/pay-method (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Affects where real wages are sent.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PAY_METHOD | payMethod must be 'ach', 'check', or 'cash' | The value is missing or not one of the three. | Send `ach`, `check` or `cash`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /staff-portal/direct-deposit`","requestBody":{"description":"The pay method.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["payMethod"],"properties":{"payMethod":{"type":"string","enum":["ach","check","cash"],"example":"ach"}}},"example":{"payMethod":"ach"}}}}}},"/staff-portal/direct-deposit":{"post":{"operationId":"StaffPortalController_addBankAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The account","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"routingNumber and accountNumber required — Either field is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"routingNumber and accountNumber required","path":"/staff-portal/direct-deposit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Add a direct deposit account","description":"Adds a bank account for wage payments. **The body carries a routing and account number** — never log it, and send it only over TLS.\n\nA wrong account number sends wages to someone else, and recovering a misdirected ACH credit is slow and often unsuccessful. Verify the digits before submitting.\n\n#### Signature\n\n```http\nPOST /staff-portal/direct-deposit (body) -> The account\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Sensitive payload; determines where wages are paid.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ACCOUNT_FIELDS_REQUIRED | routingNumber and accountNumber required | Either field is missing. | Supply both. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `DELETE /staff-portal/direct-deposit/{accountId}`","requestBody":{"description":"The bank account.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["routingNumber","accountNumber"],"properties":{"routingNumber":{"type":"string","description":"Sensitive. 9 digits.","example":"021000021"},"accountNumber":{"type":"string","description":"Sensitive.","example":"000123456789"},"accountType":{"type":"string","enum":["checking","savings"],"example":"checking"},"nickname":{"type":"string","example":"Main checking"}}},"example":{"routingNumber":"021000021","accountNumber":"000123456789","accountType":"checking"}}}}}},"/staff-portal/direct-deposit/{accountId}":{"delete":{"operationId":"StaffPortalController_removeBankAccount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"accountId","required":true,"in":"path","schema":{"type":"string"},"description":"Account id.","example":"BA-4821"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Remove a direct deposit account","description":"Removes a bank account. Removing the only account while the pay method is `ach` leaves the next payroll run with no destination — change the pay method first, or add a replacement.\n\n#### Signature\n\n```http\nDELETE /staff-portal/direct-deposit/{accountId} (accountId: string) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Can leave payroll with nowhere to pay.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /staff-portal/pay-method`"}},"/staff-portal/tax-info":{"get":{"operationId":"StaffPortalController_getTaxInfo","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Tax information","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my tax information","description":"The employee's W-4 withholding details.\n\n#### Signature\n\n```http\nGET /staff-portal/tax-info () -> Tax information\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /staff-portal/tax-info`"},"put":{"operationId":"StaffPortalController_updateTaxInfo","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Update my tax information","description":"Updates W-4 withholding. This changes how much tax is withheld from future pay — an error here shows up as an under- or over-withheld paycheck, and cannot be corrected retroactively on payslips already issued.\n\n#### Signature\n\n```http\nPUT /staff-portal/tax-info (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Changes withholding on future pay only.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/payslips`","requestBody":{"description":"W-4 fields.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"filingStatus":"single","dependents":0,"additionalWithholding":0}}}}}},"/staff-portal/schedule/upcoming":{"get":{"operationId":"StaffPortalController_upcoming","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"days","required":false,"in":"query","schema":{"type":"integer"},"description":"Look-ahead window in days.","example":14}],"responses":{"200":{"description":"Upcoming shifts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my upcoming shifts","description":"The employee's next shifts within a window of days.\n\n#### Signature\n\n```http\nGET /staff-portal/schedule/upcoming (days?: integer) -> Upcoming shifts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/schedule`"}},"/staff-portal/schedule/takeable":{"get":{"operationId":"StaffPortalController_takeable","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"example":"2026-10-05"},{"name":"days","required":false,"in":"query","schema":{"type":"integer"},"example":14}],"responses":{"200":{"description":"Takeable shifts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Shifts I could take","description":"Open shifts and shifts a colleague wants covered at my location, that I am free for. Default 14 days from `from` (today).\n\n#### Signature\n\n```http\nGET /staff-portal/schedule/takeable (from?: string, days?: integer) -> Takeable shifts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /staff-portal/schedule/{scheduleId}/shifts/{shiftId}/{action}`"}},"/staff-portal/schedule/colleagues":{"get":{"operationId":"StaffPortalController_colleagues","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"date","required":true,"in":"query","schema":{"type":"string"},"example":"2026-10-07"},{"name":"startTime","required":true,"in":"query","schema":{"type":"string"},"example":"11:00"},{"name":"endTime","required":true,"in":"query","schema":{"type":"string"},"example":"15:00"},{"name":"shiftId","required":false,"in":"query","schema":{"type":"string"},"example":"SHF-4821"}],"responses":{"200":{"description":"`{ available:[…], … }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Who could swap with me","description":"Colleagues at my location who are free for one of my shifts (me excluded).\n\n#### Signature\n\n```http\nGET /staff-portal/schedule/colleagues (date?: string, startTime?: string, endTime?: string, shiftId?: string) -> `{ available:[…], … }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/schedule/{scheduleId}/shifts/{shiftId}/{action}":{"post":{"operationId":"StaffPortalController_shiftAction","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"scheduleId","required":true,"in":"path","schema":{"type":"string"},"example":"SCH-4821"},{"name":"shiftId","required":true,"in":"path","schema":{"type":"string"},"example":"SHF-4821"},{"name":"action","required":true,"in":"path","schema":{"type":"string","enum":["accept","decline","cover","withdraw","swap","take"]},"example":"take"}],"responses":{"201":{"description":"The updated schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"That shift is already assigned — `take` on a shift someone holds and is not handing over.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"That shift is already assigned","path":"/staff-portal/schedule/{scheduleId}/shifts/{shiftId}/{action}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"That is not your shift — cover / withdraw / swap on someone else's shift.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"That is not your shift","path":"/staff-portal/schedule/{scheduleId}/shifts/{shiftId}/{action}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Shift not found — No shift on that schedule has the id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Shift not found","path":"/staff-portal/schedule/{scheduleId}/shifts/{shiftId}/{action}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"You are already working at that time — `take` would double-book me.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"You are already working at that time","path":"/staff-portal/schedule/{scheduleId}/shifts/{shiftId}/{action}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Act on a shift as me","description":"Everything I may do to a shift, checked against who I am:\n- `accept` / `decline` (with `reason`) — answer an offered shift; a decline hands it back for cover.\n- `cover` (with `reason`) — ask for someone to take my shift; it stays mine until someone does.\n- `withdraw` — take back a cover request.\n- `swap` (with `targetEmployeeId`) — ask a colleague to swap.\n- `take` — claim an open or cover-requested shift; the same readiness and double-booking checks as a manager placing me apply (`override` for a manager-approved readiness exception).\n\n#### Signature\n\n```http\nPOST /staff-portal/schedule/{scheduleId}/shifts/{shiftId}/{action} (scheduleId: string, shiftId: string, action: string, body) -> The updated schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | SHIFT_NOT_FOUND | Shift not found | No shift on that schedule has the id. | — |\n| `403` | NOT_YOUR_SHIFT | That is not your shift | cover / withdraw / swap on someone else's shift. | — |\n| `400` | SHIFT_TAKEN | That shift is already assigned | `take` on a shift someone holds and is not handing over. | — |\n| `409` | SHIFT_CONFLICT | You are already working at that time | `take` would double-book me. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"},"targetEmployeeId":{"type":"string"},"override":{"type":"object","additionalProperties":true}}},"example":{"reason":"Doctor appointment"}}}}}},"/staff-portal/schedule":{"get":{"operationId":"StaffPortalController_schedule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-09-01"},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"ISO date.","example":"2026-09-30"}],"responses":{"200":{"description":"Shifts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my schedule","description":"The employee's shifts over an explicit date range.\n\n#### Signature\n\n```http\nGET /staff-portal/schedule (from?: string, to?: string) -> Shifts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/schedule/upcoming`"}},"/staff-portal/timesheets/current":{"get":{"operationId":"StaffPortalController_currentTimesheet","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The current timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my current timesheet","description":"The timesheet for the pay period in progress — what a \"this week\" view shows.\n\n#### Signature\n\n```http\nGET /staff-portal/timesheets/current () -> The current timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /staff-portal/timesheets/{timesheetId}/submit`"}},"/staff-portal/timesheets":{"get":{"operationId":"StaffPortalController_listTimesheets","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"p","in":"query","required":false,"description":"Page number.","schema":{"type":"integer"},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"},"example":20}],"responses":{"200":{"description":"Timesheets","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"List my timesheets","description":"The employee's timesheets, newest first.\n\n#### Signature\n\n```http\nGET /staff-portal/timesheets (p?: integer, ps?: integer) -> Timesheets\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/timesheets/current`"}},"/staff-portal/timesheets/{timesheetId}/entries":{"post":{"operationId":"StaffPortalController_addEntry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"timesheetId","required":true,"in":"path","schema":{"type":"string"},"description":"Timesheet id.","example":"TS-4821"}],"responses":{"201":{"description":"The entry","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Add a time entry","description":"Adds an entry to a timesheet manually — for time worked that was not clocked. Entries feed the hours payroll pays on, so they are checked at approval.\n\n#### Signature\n\n```http\nPOST /staff-portal/timesheets/{timesheetId}/entries (timesheetId: string, body) -> The entry\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /staff-portal/timesheets/clock-in`","requestBody":{"description":"The entry.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"date":"2026-08-30","start":"09:00","end":"17:00","note":"Forgot to clock in"}}}}}},"/staff-portal/timesheets/clock-in":{"post":{"operationId":"StaffPortalController_clockIn","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The open session","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"423":{"description":"<name> can't clock in: <requirement titles>. — A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"reason":"readiness_block","code":"READINESS_BLOCK","gate":"clockIn","message":"Luis Ramirez can't clock in: Food handler card.","employeeId":"E012","employeeName":"Luis Ramirez","effect":"block","blocking":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-09-20T00:00:00.000Z"}],"warnings":[],"reasons":[{"requirementId":"REQ-FOOD01","title":"Food handler card","kind":"certification","status":"expired","severity":"block"}],"canOverride":true,"override":{"how":"Send the same request again with override: { reasonCode, reason, expiresAt }.","fields":["reasonCode","reason","expiresAt"],"reasonCodes":["renewal_booked","awaiting_document","training_scheduled","business_need","not_applicable","system_error","other"],"approver":"A location manager or HR. The person’s own supervisor alone cannot approve."}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Clock in","description":"Starts a work session. Only one can be open at a time — check `timesheets/clock-in/active` before starting another.\n\n#### Signature\n\n```http\nPOST /staff-portal/timesheets/clock-in (body) -> The open session\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Same clockIn gate as the admin clock (423 `readiness_block` with `blocking[]` and `trainingAtClockIn[]`; success may carry `readiness.warnings` and `trainingAtClockIn[]`). The person cannot override their own block — a manager clocks them in with an override from the admin clock.\n- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `423` | READINESS_BLOCK | <name> can't clock in: <requirement titles>. | A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /staff-portal/timesheets/clock-out`","requestBody":{"description":"Optional clock-in detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"location":"Store 3"}}}}}},"/staff-portal/timesheets/clock-out":{"post":{"operationId":"StaffPortalController_clockOut","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The closed session","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Clock out","description":"Closes the open work session and writes the hours to the timesheet. Forgetting to clock out leaves the session running and the hours wrong — the fix is a manual entry, which needs approval.\n\n#### Signature\n\n```http\nPOST /staff-portal/timesheets/clock-out (body) -> The closed session\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/timesheets/clock-in/active`","requestBody":{"description":"Optional clock-out detail.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/staff-portal/timesheets/clock-in/active":{"get":{"operationId":"StaffPortalController_activeClock","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The active session, or none","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my active clock-in","description":"The open work session, if there is one — what a \"you are still clocked in\" banner reads.\n\n#### Signature\n\n```http\nGET /staff-portal/timesheets/clock-in/active () -> The active session, or none\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /staff-portal/timesheets/clock-out`"}},"/staff-portal/timesheets/{timesheetId}/submit":{"post":{"operationId":"StaffPortalController_submit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"timesheetId","required":true,"in":"path","schema":{"type":"string"},"description":"Timesheet id.","example":"TS-4821"}],"responses":{"201":{"description":"The submitted timesheet","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Submit a timesheet","description":"Submits a timesheet for approval. Once submitted it is generally locked to further edits — clock out and check the entries first, because a correction after submission goes through a manager.\n\n#### Signature\n\n```http\nPOST /staff-portal/timesheets/{timesheetId}/submit (timesheetId: string) -> The submitted timesheet\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Locks the timesheet to further self-service edits.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/timesheets/current`"}},"/staff-portal/payslips":{"get":{"operationId":"StaffPortalController_listPayslips","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"p","in":"query","required":false,"description":"Page number.","schema":{"type":"integer"},"example":1},{"name":"ps","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"},"example":20}],"responses":{"200":{"description":"Payslips","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"List my payslips","description":"The employee's pay stubs, newest first.\n\n#### Signature\n\n```http\nGET /staff-portal/payslips (p?: integer, ps?: integer) -> Payslips\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/payslips/{stubId}`"}},"/staff-portal/payslips/year/{year}/summary":{"get":{"operationId":"StaffPortalController_yearSummary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":true,"in":"path","schema":{"type":"string"},"description":"Calendar year.","example":"2026"}],"responses":{"200":{"description":"The year summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get a year-to-date summary","description":"Earnings, taxes and deductions totalled for a calendar year.\n\n#### Signature\n\n```http\nGET /staff-portal/payslips/year/{year}/summary (year: string) -> The year summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/w2/{year}`"}},"/staff-portal/payslips/{stubId}":{"get":{"operationId":"StaffPortalController_getPayslip","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"stubId","required":true,"in":"path","schema":{"type":"string"},"description":"Pay stub id.","example":"PS-4821"}],"responses":{"200":{"description":"The payslip","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get a payslip","description":"One pay stub with its earnings, deductions and taxes.\n\n#### Signature\n\n```http\nGET /staff-portal/payslips/{stubId} (stubId: string) -> The payslip\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/payslips/{stubId}/pdf`"}},"/staff-portal/payslips/{stubId}/pdf":{"get":{"operationId":"StaffPortalController_payslipPdf","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"stubId","required":true,"in":"path","schema":{"type":"string"},"description":"Pay stub id.","example":"PS-4821"}],"responses":{"200":{"description":"The payslip PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Download a payslip PDF","description":"The pay stub as a PDF — what an employee sends to a landlord or a lender. Binary response, not JSON.\n\n#### Signature\n\n```http\nGET /staff-portal/payslips/{stubId}/pdf (stubId: string) -> The payslip PDF\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/payslips/{stubId}`"}},"/staff-portal/w2/{year}":{"get":{"operationId":"StaffPortalController_w2","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2025"}],"responses":{"200":{"description":"The W-2","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my W-2","description":"The employee's W-2 for a tax year — a statutory tax document, so the figures must match what was filed.\n\n#### Signature\n\n```http\nGET /staff-portal/w2/{year} (year: string) -> The W-2\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/w2/{year}/pdf`"}},"/staff-portal/w2/{year}/pdf":{"get":{"operationId":"StaffPortalController_w2Pdf","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"year","required":true,"in":"path","schema":{"type":"string"},"description":"Tax year.","example":"2025"}],"responses":{"200":{"description":"The W-2 PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Download my W-2 PDF","description":"The W-2 as a PDF, in the form an employee files with. Binary response, not JSON.\n\n#### Signature\n\n```http\nGET /staff-portal/w2/{year}/pdf (year: string) -> The W-2 PDF\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/w2/{year}`"}},"/staff-portal/deductions":{"get":{"operationId":"StaffPortalController_deductions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Deductions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff portal"],"summary":"Get my deductions","description":"Recurring deductions from the employee's pay — benefits, garnishments, contributions.\n\n#### Signature\n\n```http\nGET /staff-portal/deductions () -> Deductions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /staff-portal/payslips`"}},"/print/templates":{"get":{"operationId":"PrintController_templates","summary":"List printable datatypes and templates","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Datatypes and templates","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Print"],"description":"Which record types can be printed and what templates exist for each.\n\n#### Signature\n\n```http\nGET /print/templates () -> Datatypes and templates\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /print/{datatype}/{id}/pdf`"}},"/print/{datatype}/{id}/pdf":{"get":{"operationId":"PrintController_pdf","summary":"Render a record as PDF","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"200":{"description":"The rendered PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Print"],"description":"Renders a record to a full-page PDF — an invoice, a booking confirmation. Binary response, not JSON.\n\n#### Signature\n\n```http\nGET /print/{datatype}/{id}/pdf (datatype: string, id: string) -> The rendered PDF\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /print/{datatype}/{id}/thermal`"}},"/print/{datatype}/{id}/thermal":{"get":{"operationId":"PrintController_thermal","summary":"Render a record for a thermal printer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"200":{"description":"The ESC/POS line tree","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Print"],"description":"Renders a record as an ESC/POS line tree for a receipt printer — the structure a thermal printer consumes, not a page layout.\n\n#### Signature\n\n```http\nGET /print/{datatype}/{id}/thermal (datatype: string, id: string) -> The ESC/POS line tree\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /device-integrations/call`"}},"/print/{datatype}/{id}":{"post":{"operationId":"PrintController_render","summary":"Render a record in a chosen format","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"datatype","required":true,"in":"path","schema":{"type":"string"},"description":"DataType of the record.","example":"product"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"PRD-4821"}],"responses":{"201":{"description":"The rendered output — a PDF or a line tree, per `format`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Print"],"description":"Renders a record as `pdf` or `thermal`, chosen in the body. The one endpoint to call when the format is decided at runtime; the response type follows the format requested.\n\n#### Signature\n\n```http\nPOST /print/{datatype}/{id} (datatype: string, id: string, body) -> The rendered output — a PDF or a line tree, per `format`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /print/{datatype}/{id}/pdf`","requestBody":{"description":"The format and options.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string","enum":["pdf","thermal"],"example":"pdf"},"template":{"type":"string","example":"invoice-default"}}},"example":{"format":"pdf","template":"invoice-default"}}}}}},"/creative-studio/designs/list":{"post":{"operationId":"CreativeStudioController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ data, total, page, pageSize, hasNext, sort, search }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"sk":{"type":"string"},"datatype":{"type":"string","example":"creative_studio"},"name":{"type":"string","description":"Unique slug, generated from the title.","example":"fall-sale-post-k3m9x"},"title":{"type":"string","example":"Fall sale post"},"type":{"type":"string","enum":["design","image"]},"status":{"type":"string","example":"draft"},"thumbnail":{"type":"object","nullable":true,"properties":{"path":{"type":"string"},"url":{"type":"string"},"contentType":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"}}},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"frameCount":{"type":"integer"},"hasContent":{"type":"boolean"},"createdate":{"type":"string"},"modifydate":{"type":"string"}}}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"},"hasNext":{"type":"boolean"},"sort":{"type":"string"},"search":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Creative Studio"],"summary":"List the design library","description":"One page of the library, searched, sorted and paged on the server, without the canvas JSON. `search` matches title, name and tags (case-insensitive). `sort`: `recent` (last modified, default), `oldest` (created), or `name` (title, case-insensitive). `type` narrows to uploaded `image`s or to `design`s. A POST because of the body — it is a read.\n\n#### Signature\n\n```http\nPOST /creative-studio/designs/list (body) -> { data, total, page, pageSize, hasNext, sort, search }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"The page to show.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"search":{"type":"string"},"sort":{"type":"string","enum":["recent","oldest","name"],"default":"recent"},"type":{"type":"string","enum":["design","image"]},"page":{"type":"integer","default":1},"pageSize":{"type":"integer","default":24,"maximum":100}}},"example":{"search":"fall","sort":"recent","page":1,"pageSize":24}}}}}},"/creative-studio/designs/create":{"post":{"operationId":"CreativeStudioController_create","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The design summary","content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"datatype":{"type":"string","example":"creative_studio"},"name":{"type":"string","description":"Unique slug, generated from the title.","example":"fall-sale-post-k3m9x"},"title":{"type":"string","example":"Fall sale post"},"type":{"type":"string","enum":["design","image"]},"status":{"type":"string","example":"draft"},"thumbnail":{"type":"object","nullable":true,"properties":{"path":{"type":"string"},"url":{"type":"string"},"contentType":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"}}},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"frameCount":{"type":"integer"},"hasContent":{"type":"boolean"},"createdate":{"type":"string"},"modifydate":{"type":"string"}}}}}},"400":{"description":"A size between 16 and 10000 pixels on each side is required — Width or height is missing or out of range.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A size between 16 and 10000 pixels on each side is required","path":"/creative-studio/designs/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The design could not be saved — The record was not created.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":500,"error":"The design could not be saved","path":"/creative-studio/designs/create","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Creative Studio"],"summary":"Create a design","description":"A new, empty design with one white frame of the chosen size, ready to open in the editor. Without a title it is named after the size and today's date. The unique `name` is generated.\n\n#### Signature\n\n```http\nPOST /creative-studio/designs/create (body) -> The design summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SIZE_INVALID | A size between 16 and 10000 pixels on each side is required | Width or height is missing or out of range. | — |\n| `500` | SAVE_FAILED | The design could not be saved | The record was not created. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`.","requestBody":{"description":"The size and name.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["size"],"properties":{"title":{"type":"string"},"platform":{"type":"string"},"size":{"type":"object","required":["width","height"],"properties":{"width":{"type":"integer","minimum":16,"maximum":10000},"height":{"type":"integer","minimum":16,"maximum":10000},"name":{"type":"string"},"platform":{"type":"string"}}}}},"example":{"title":"Fall sale post","size":{"width":1080,"height":1080,"name":"Instagram post","platform":"instagram"}}}}}}},"/creative-studio/designs/{id}/save":{"post":{"operationId":"CreativeStudioController_save","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Design `sk`.","example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"The summary plus the saved `record`","content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"datatype":{"type":"string","example":"creative_studio"},"name":{"type":"string","description":"Unique slug, generated from the title.","example":"fall-sale-post-k3m9x"},"title":{"type":"string","example":"Fall sale post"},"type":{"type":"string","enum":["design","image"]},"status":{"type":"string","example":"draft"},"thumbnail":{"type":"object","nullable":true,"properties":{"path":{"type":"string"},"url":{"type":"string"},"contentType":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"}}},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"frameCount":{"type":"integer"},"hasContent":{"type":"boolean"},"createdate":{"type":"string"},"modifydate":{"type":"string"},"record":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"A design needs a name — The title is present but empty after trimming.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A design needs a name","path":"/creative-studio/designs/{id}/save","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Design not found — No design with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Design not found","path":"/creative-studio/designs/{id}/save","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Creative Studio"],"summary":"Save a design from the editor","description":"The editor's save. Only these fields are written: `content`, `workAreas`, `activeWorkAreaId`, `viewport`, `designSystem`, `externalContext`, `platform`, `format`, `tags`, plus `title`; everything else is server-owned. The design's width and height follow its first frame. Send `thumbnail` (a data URL) to store a fresh preview in the same call.\n\n#### Signature\n\n```http\nPOST /creative-studio/designs/{id}/save (id: string, body) -> The summary plus the saved `record`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DESIGN_NOT_FOUND | Design not found | No design with that id. | — |\n| `400` | NAME_REQUIRED | A design needs a name | The title is present but empty after trimming. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"The fields to save.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"content":{"type":"object","additionalProperties":true},"workAreas":{"type":"array","items":{"type":"object","additionalProperties":true}},"activeWorkAreaId":{"type":"string"},"viewport":{"type":"object","additionalProperties":true},"designSystem":{"type":"object","additionalProperties":true},"externalContext":{"type":"object","additionalProperties":true},"platform":{"type":"string"},"format":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"thumbnail":{"type":"string","description":"PNG/JPEG/WebP data URL."}}},"example":{"content":{"objects":[]},"workAreas":[{"id":"fa1","width":1080,"height":1080}],"thumbnail":"data:image/png;base64,iVBORw0…"}}}}}},"/creative-studio/designs/{id}/rename":{"post":{"operationId":"CreativeStudioController_rename","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Design `sk`.","example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"The design summary","content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"datatype":{"type":"string","example":"creative_studio"},"name":{"type":"string","description":"Unique slug, generated from the title.","example":"fall-sale-post-k3m9x"},"title":{"type":"string","example":"Fall sale post"},"type":{"type":"string","enum":["design","image"]},"status":{"type":"string","example":"draft"},"thumbnail":{"type":"object","nullable":true,"properties":{"path":{"type":"string"},"url":{"type":"string"},"contentType":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"}}},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"frameCount":{"type":"integer"},"hasContent":{"type":"boolean"},"createdate":{"type":"string"},"modifydate":{"type":"string"}}}}}},"400":{"description":"A design needs a name — The title is present but empty after trimming.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A design needs a name","path":"/creative-studio/designs/{id}/rename","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Design not found — No design with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Design not found","path":"/creative-studio/designs/{id}/rename","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Creative Studio"],"summary":"Rename a design","description":"Changes the title shown in the library. The unique `name` stays as it is.\n\n#### Signature\n\n```http\nPOST /creative-studio/designs/{id}/rename (id: string, body) -> The design summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | NAME_REQUIRED | A design needs a name | The title is present but empty after trimming. | — |\n| `404` | DESIGN_NOT_FOUND | Design not found | No design with that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"The new title.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"}}},"example":{"title":"Fall sale — final"}}}}}},"/creative-studio/designs/{id}/duplicate":{"post":{"operationId":"CreativeStudioController_duplicate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Design `sk`.","example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"The new design's summary","content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"datatype":{"type":"string","example":"creative_studio"},"name":{"type":"string","description":"Unique slug, generated from the title.","example":"fall-sale-post-k3m9x"},"title":{"type":"string","example":"Fall sale post"},"type":{"type":"string","enum":["design","image"]},"status":{"type":"string","example":"draft"},"thumbnail":{"type":"object","nullable":true,"properties":{"path":{"type":"string"},"url":{"type":"string"},"contentType":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"}}},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"frameCount":{"type":"integer"},"hasContent":{"type":"boolean"},"createdate":{"type":"string"},"modifydate":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Design not found — No design with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Design not found","path":"/creative-studio/designs/{id}/duplicate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Creative Studio"],"summary":"Duplicate a design","description":"A full copy — canvas, frames, brand kit and its own copy of the preview — titled \"<title> (copy)\", as a draft. Export history, finalised state and external context are not copied. If the preview cannot be copied the copy has none until its first save.\n\n#### Signature\n\n```http\nPOST /creative-studio/designs/{id}/duplicate (id: string) -> The new design's summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DESIGN_NOT_FOUND | Design not found | No design with that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/creative-studio/designs/{id}/thumbnail":{"post":{"operationId":"CreativeStudioController_thumbnail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Design `sk`.","example":"66f1c0ffee12ab34cd56ef78"}],"responses":{"201":{"description":"{ sk, thumbnail }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The preview must be a PNG, JPEG or WebP data URL — The preview is not a `data:image/png|jpeg|webp;base64,…` URL (or it is empty: \"The preview is empty\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The preview must be a PNG, JPEG or WebP data URL","path":"/creative-studio/designs/{id}/thumbnail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Design not found — No design with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Design not found","path":"/creative-studio/designs/{id}/thumbnail","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Creative Studio"],"summary":"Store a design preview","description":"Stores a rendered preview (resized to fit the library thumbnail, saved as PNG) and points the design at it. The URL carries a version so browsers do not show the old one.\n\n#### Signature\n\n```http\nPOST /creative-studio/designs/{id}/thumbnail (id: string, body) -> { sk, thumbnail }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | DESIGN_NOT_FOUND | Design not found | No design with that id. | — |\n| `400` | PREVIEW_INVALID | The preview must be a PNG, JPEG or WebP data URL | The preview is not a `data:image/png\\|jpeg\\|webp;base64,…` URL (or it is empty: \"The preview is empty\"). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"The preview.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","description":"PNG/JPEG/WebP data URL."}}},"example":{"image":"data:image/png;base64,iVBORw0…"}}}}}},"/creative-studio/assets":{"post":{"operationId":"CreativeStudioController_addAsset","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The new image item's summary","content":{"application/json":{"schema":{"type":"object","properties":{"sk":{"type":"string"},"datatype":{"type":"string","example":"creative_studio"},"name":{"type":"string","description":"Unique slug, generated from the title.","example":"fall-sale-post-k3m9x"},"title":{"type":"string","example":"Fall sale post"},"type":{"type":"string","enum":["design","image"]},"status":{"type":"string","example":"draft"},"thumbnail":{"type":"object","nullable":true,"properties":{"path":{"type":"string"},"url":{"type":"string"},"contentType":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"}}},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"frameCount":{"type":"integer"},"hasContent":{"type":"boolean"},"createdate":{"type":"string"},"modifydate":{"type":"string"}}}}}},"400":{"description":"The uploaded file path is required — No `path`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The uploaded file path is required","path":"/creative-studio/assets","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Creative Studio"],"summary":"Add an uploaded image to the library","description":"Turns a file already uploaded to the org's storage into a library item: a design of `type: \"image\"` with one frame the size of the picture, the picture waiting to be placed in it, and a preview made from the file. Upload the file first (repository file upload), then send its `path`.\n\n#### Signature\n\n```http\nPOST /creative-studio/assets (body) -> The new image item's summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | PATH_REQUIRED | The uploaded file path is required | No `path`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"description":"The uploaded file.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"Storage path of the upload (with or without the org prefix)."},"url":{"type":"string","description":"URL to use for the picture; a signed URL is made when omitted."},"name":{"type":"string","description":"File name; becomes the title without its extension."}}},"example":{"path":"uploads/team-photo.jpg","name":"team-photo.jpg"}}}}}},"/profile":{"get":{"operationId":"AppController_getProfile","summary":"Get the caller's profile","description":"Returns the authenticated user attached to the request — the quickest way to see who a token resolves to and what it carries.\n\n#### Signature\n\n```http\nGET /profile () -> The authenticated user\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/me`","parameters":[],"responses":{"200":{"description":"The authenticated user","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Platform"]}},"/":{"get":{"operationId":"AppController_getAll_get","parameters":[],"responses":{"200":{"description":"A greeting","content":{"application/json":{"schema":{"type":"string"},"example":"Hello World!"}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Platform"],"summary":"Hello world","description":"A greeting. The simplest possible proof that the server is answering HTTP at all.\n\n#### Signature\n\n```http\nGET / () -> A greeting\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /health`"},"post":{"operationId":"AppController_getAll_post","parameters":[],"responses":{"200":{"description":""}},"tags":["App"]},"put":{"operationId":"AppController_getAll_put","parameters":[],"responses":{"200":{"description":""}},"tags":["App"]},"delete":{"operationId":"AppController_getAll_delete","parameters":[],"responses":{"200":{"description":""}},"tags":["App"]},"patch":{"operationId":"AppController_getAll_patch","parameters":[],"responses":{"200":{"description":""}},"tags":["App"]},"options":{"operationId":"AppController_getAll_options","parameters":[],"responses":{"200":{"description":""}},"tags":["App"]},"head":{"operationId":"AppController_getAll_head","parameters":[],"responses":{"200":{"description":""}},"tags":["App"]},"search":{"operationId":"AppController_getAll_search","parameters":[],"responses":{"200":{"description":""}},"tags":["App"]}},"/version":{"get":{"operationId":"AppController_version","summary":"Get the application version","description":"The running build's version — the first thing to check when behaviour differs from what the code says.\n\n#### Signature\n\n```http\nGET /version () -> Version information\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /health`","parameters":[],"responses":{"200":{"description":"Version information","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Platform"]}},"/health":{"get":{"operationId":"AppController_healthCheck","summary":"Health check","description":"Liveness check for monitoring. Sends `Cache-Control: no-cache, no-store, must-revalidate`, so a proxy cannot serve a stale healthy answer for a process that has since died.\n\nThis path is exempt from rate limiting. For per-component detail, use `GET /monitoring/health`.\n\n#### Signature\n\n```http\nGET /health () -> Health status\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /readiness`\n- `GET /monitoring/health`","parameters":[],"responses":{"200":{"description":"Health status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"System is unhealthy"}},"tags":["Platform"]}},"/readiness":{"get":{"operationId":"AppController_readinessCheck","summary":"Readiness check","parameters":[],"responses":{"200":{"description":"Readiness status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"Application is not ready"}},"tags":["Platform"],"description":"Kubernetes readiness probe: whether the instance is ready to take traffic, which is not the same as being alive. A process can be healthy while still warming up, and routing traffic to it then produces errors.\n\n#### Signature\n\n```http\nGET /readiness () -> Readiness status\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `GET /health`"}},"/profile/security/devices":{"get":{"operationId":"SecurityController_listDevices","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The account's devices","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List my devices","description":"The devices that have signed in to the caller's account, with when each was last seen. The read behind a \"where you're signed in\" screen, and the first place a user looks after a suspicious-login alert.\n\n#### Signature\n\n```http\nGET /profile/security/devices () -> The account's devices\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/security/login-history`","tags":["Users · Security"]}},"/profile/security/devices/current":{"get":{"operationId":"SecurityController_getCurrentDevice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The current device","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get the current device","description":"Identifies the device making this request, so a device list can mark which entry is \"this one\" and avoid the user revoking their own session by mistake.\n\n#### Signature\n\n```http\nGET /profile/security/devices/current () -> The current device\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/security/devices`","tags":["Users · Security"]}},"/profile/security/devices/{deviceId}/trust":{"post":{"operationId":"SecurityController_trustDevice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"},"description":"Device id.","example":"DEV-4821"}],"responses":{"201":{"description":"The trusted device","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Trust a device","description":"Marks a device as trusted, which typically lets it skip two-factor prompts for a period.\n\n**This deliberately weakens a protection.** Trust only a device the user controls, and keep `durationDays` short — a trusted device on shared hardware defeats the second factor entirely.\n\n#### Signature\n\n```http\nPOST /profile/security/devices/{deviceId}/trust (deviceId: string, body) -> The trusted device\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Never offer this on a public or shared machine.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/devices/{deviceId}/block`","tags":["Users · Security"],"requestBody":{"description":"How long to trust it.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"durationDays":{"type":"integer","description":"Trust duration. Shorter is safer.","example":30}}},"example":{"durationDays":30}}}}}},"/profile/security/devices/{deviceId}/block":{"post":{"operationId":"SecurityController_blockDevice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"},"description":"Device id.","example":"DEV-4821"}],"responses":{"201":{"description":"The blocked device","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Block a device","description":"Blocks a device from signing in — the immediate response to an unrecognised entry in the device list. Blocking does not end an existing session on its own, so sign the account out as well if the session may still be live.\n\n#### Signature\n\n```http\nPOST /profile/security/devices/{deviceId}/block (deviceId: string, body) -> The blocked device\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Pair with a sign-out and a password change if the device may have an active session.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /signout`\n- `POST /password/change`","tags":["Users · Security"],"requestBody":{"description":"Why it was blocked.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Unrecognised device in Berlin"}}},"example":{"reason":"Unrecognised device"}}}}}},"/profile/security/devices/{deviceId}":{"delete":{"operationId":"SecurityController_removeDevice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"},"description":"Device id.","example":"DEV-4821"}],"responses":{"200":{"description":"Removal result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Remove a device","description":"Removes a device from the account. Unlike blocking, this forgets it — the same device signing in again is treated as new, which is usually what you want after replacing hardware.\n\n#### Signature\n\n```http\nDELETE /profile/security/devices/{deviceId} (deviceId: string) -> Removal result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/devices/{deviceId}/block`","tags":["Users · Security"]}},"/profile/security/login-history":{"get":{"operationId":"SecurityController_getLoginHistory","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Login history","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get login history","description":"Recent sign-in attempts for the account, successful and failed. Failed attempts from unfamiliar locations are the signal worth acting on.\n\n#### Signature\n\n```http\nGET /profile/security/login-history () -> Login history\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/security/devices`","tags":["Users · Security"]}},"/profile/security/2fa/status":{"get":{"operationId":"SecurityController_getTwoFactorStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Two-factor status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get two-factor status","description":"Whether two-factor authentication is enabled for the account and which method is configured.\n\n#### Signature\n\n```http\nGET /profile/security/2fa/status () -> Two-factor status\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/2fa/setup/{method}`","tags":["Users · Security"]}},"/profile/security/2fa/setup/{method}":{"post":{"operationId":"SecurityController_setupTwoFactor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"method","required":true,"in":"path","schema":{"type":"string"},"description":"Two-factor method, e.g. `totp`, `sms`, `email`.","example":"totp"}],"responses":{"201":{"description":"Setup material — secret, QR code or confirmation a code was sent","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Set up two-factor authentication","description":"Begins two-factor setup for a method, returning what the user needs to complete it — a QR code for an authenticator app, or triggering a code to a phone.\n\nSetup is **not** enablement: the user must verify a code first, which proves their device actually works before the account depends on it.\n\n#### Signature\n\n```http\nPOST /profile/security/2fa/setup/{method} (method: string, body) -> Setup material — secret, QR code or confirmation a code was sent\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The setup secret is a credential. Do not log it.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/2fa/verify-setup`","tags":["Users · Security"],"requestBody":{"description":"Method-specific detail.","required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"Required for SMS.","example":"+15551234567"}}},"example":{"phone":"+15551234567"}}}}}},"/profile/security/2fa/verify-setup":{"post":{"operationId":"SecurityController_verifyTwoFactorSetup","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Verification result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid or expired code — The verification code is wrong or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or expired code","path":"/profile/security/2fa/verify-setup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Verify two-factor setup","description":"Confirms the user can produce a valid code from their newly configured method. This is the check that stops someone locking themselves out by enabling 2FA against a device that does not work.\n\n#### Signature\n\n```http\nPOST /profile/security/2fa/verify-setup (body) -> Verification result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CODE | Invalid or expired code | The verification code is wrong or has expired. | Codes are short-lived. Request a new one rather than retrying an old code. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/2fa/enable`","tags":["Users · Security"],"requestBody":{"description":"The code from the authenticator or message.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","example":"481625"}}},"example":{"code":"481625"}}}}}},"/profile/security/2fa/enable":{"post":{"operationId":"SecurityController_enableTwoFactor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid or expired code — The verification code is wrong or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or expired code","path":"/profile/security/2fa/enable","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Enable two-factor authentication","description":"Turns two-factor on for the account, requiring a valid code to confirm. Generate backup codes immediately afterwards — without them, a lost device means an account recovery.\n\n#### Signature\n\n```http\nPOST /profile/security/2fa/enable (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CODE | Invalid or expired code | The verification code is wrong or has expired. | Codes are short-lived. Request a new one rather than retrying an old code. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/2fa/backup-codes`","tags":["Users · Security"],"requestBody":{"description":"A current code.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","example":"481625"}}},"example":{"code":"481625"}}}}}},"/profile/security/2fa/factors":{"get":{"operationId":"SecurityController_listFactors","summary":"List my second factors","description":"Every second factor on the caller's account — people enrol more than one (an authenticator, a phone, an email) and any verified one can answer a challenge. The default is challenged first; the list is ordered by preference. Secrets are never returned. Works for staff users and customers.\n\n#### Signature\n\n```http\nGET /profile/security/2fa/factors () -> { twoFactorEnabled, defaultFactorId, factors }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/2fa/factors/{factorId}/default`\n- `DELETE /profile/security/2fa/factors/{factorId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ twoFactorEnabled, defaultFactorId, factors }","content":{"application/json":{"schema":{"type":"object","properties":{"twoFactorEnabled":{"type":"boolean","description":"True while at least one factor is verified."},"defaultFactorId":{"type":"string","nullable":true},"factors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["authenticator","sms","email"]},"label":{"type":"string","description":"The phone or email for sms/email factors, else \"Authenticator app\"."},"status":{"type":"string","example":"verified"},"isDefault":{"type":"boolean"},"verifiedAt":{"type":"string"},"lastUsedAt":{"type":"string"}}}}}},"example":{"twoFactorEnabled":true,"defaultFactorId":"f1","factors":[{"id":"f1","type":"authenticator","label":"Authenticator app","status":"verified","isDefault":true,"verifiedAt":"2026-08-02T10:00:00.000Z"},{"id":"f2","type":"sms","label":"+1 555 0100","status":"verified","isDefault":false}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Security"]}},"/profile/security/2fa/factors/{factorId}/default":{"post":{"operationId":"SecurityController_setDefaultFactor","summary":"Choose which factor is challenged first","description":"Makes one enrolled factor the default. Only a verified factor can be the default, or sign-in would lead with a code that has never worked.\n\n#### Signature\n\n```http\nPOST /profile/security/2fa/factors/{factorId}/default (factorId: string) -> { factorId, isDefault: true }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NO_SUCH_FACTOR | No such factor on this account | `factorId` is not one of the caller's enrolled factors. | — |\n| `400` | FACTOR_NOT_VERIFIED | Confirm that factor before making it the default | The factor was enrolled but never verified. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/security/2fa/factors`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"factorId","required":true,"in":"path","schema":{"type":"string"},"description":"Factor id from the factor list.","example":"f1"}],"responses":{"201":{"description":"{ factorId, isDefault: true }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Confirm that factor before making it the default — The factor was enrolled but never verified.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Confirm that factor before making it the default","path":"/profile/security/2fa/factors/{factorId}/default","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such factor on this account — `factorId` is not one of the caller's enrolled factors.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such factor on this account","path":"/profile/security/2fa/factors/{factorId}/default","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Security"]}},"/profile/security/2fa/factors/{factorId}":{"delete":{"operationId":"SecurityController_removeFactor","summary":"Remove one second factor","description":"Removes one enrolled factor; the others stay. When the default is removed the next verified factor becomes the default. Removing the last verified factor **turns 2FA off** and deletes the backup codes, rather than leaving the account demanding a code nothing can produce.\n\n#### Signature\n\n```http\nDELETE /profile/security/2fa/factors/{factorId} (factorId: string) -> { factorId, removed, remaining, twoFactorEnabled }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Removing the last factor weakens the account — worth alerting on.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NO_SUCH_FACTOR | No such factor on this account | `factorId` is not one of the caller's enrolled factors. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/security/2fa/factors`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"factorId","required":true,"in":"path","schema":{"type":"string"},"description":"Factor id from the factor list.","example":"f1"}],"responses":{"200":{"description":"{ factorId, removed, remaining, twoFactorEnabled }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"factorId":"f2","removed":true,"remaining":1,"twoFactorEnabled":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such factor on this account — `factorId` is not one of the caller's enrolled factors.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No such factor on this account","path":"/profile/security/2fa/factors/{factorId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Users · Security"]}},"/profile/security/2fa/disable":{"post":{"operationId":"SecurityController_disableTwoFactor","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid Password — The password is wrong.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid Password","path":"/profile/security/2fa/disable","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Disable two-factor authentication","description":"Turns two-factor off.\n\nIt requires the **password**, not a code — deliberately, since someone who has lost their second factor still needs a way out. That also makes this the endpoint an attacker with a stolen password would reach for, so it is worth alerting the user whenever it succeeds.\n\n#### Signature\n\n```http\nPOST /profile/security/2fa/disable (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Notify the account owner out of band when 2FA is disabled.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_PASSWORD | Invalid Password | The password is wrong. | Two-factor stays enabled. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/2fa/enable`","tags":["Users · Security"],"requestBody":{"description":"The account password.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","format":"password"}}},"example":{"password":"…"}}}}}},"/profile/security/2fa/backup-codes":{"post":{"operationId":"SecurityController_generateBackupCodes","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The backup codes — displayed once","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Generate backup codes","description":"Generates single-use recovery codes for when the second factor is unavailable.\n\nThey are shown **once** and each works once. Generating a new set invalidates the old one, so a user who regenerates without saving the new codes has thrown away their recovery route.\n\n#### Signature\n\n```http\nPOST /profile/security/2fa/backup-codes () -> The backup codes — displayed once\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Regenerating invalidates the previous set. Make the user save them before leaving the screen.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/security/2fa/backup-codes/count`","tags":["Users · Security"]}},"/profile/security/2fa/backup-codes/count":{"get":{"operationId":"SecurityController_getBackupCodesCount","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Remaining code count","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Count remaining backup codes","description":"How many unused backup codes remain — the prompt to regenerate before a user runs out entirely.\n\n#### Signature\n\n```http\nGET /profile/security/2fa/backup-codes/count () -> Remaining code count\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/2fa/backup-codes`","tags":["Users · Security"]}},"/profile/security/settings":{"get":{"operationId":"SecurityController_getSecuritySettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Security settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get security settings","description":"The caller's security preferences — device trust, new-device alerts and preferred two-factor method. For a staff user it also carries `passwordSelfService`: `{ canChange, canReset, needsCurrentPassword, policy, source, rules }` from the password policy that governs them (source `user` | `group` | `role` | `default` | `none`). Account screens show or hide \"Change password\" from `canChange`; the change route enforces the same rule.\n\n#### Signature\n\n```http\nGET /profile/security/settings () -> Security settings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PATCH /profile/security/settings`","tags":["Users · Security"]},"patch":{"operationId":"SecurityController_updateSecuritySettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The updated settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update security settings","description":"Updates the caller's security preferences.\n\nTurning `alertOnNewDevice` off removes the notification that would tell the user about an unfamiliar sign-in — that is the setting an attacker would change first, so treat a change to it as worth logging.\n\n#### Signature\n\n```http\nPATCH /profile/security/settings (body) -> The updated settings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Disabling new-device alerts is a meaningful reduction in protection.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /profile/security/settings`","tags":["Users · Security"],"requestBody":{"description":"Settings to change.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceTrustEnabled":{"type":"boolean","description":"Allow devices to be trusted and skip 2FA prompts.","example":true},"alertOnNewDevice":{"type":"boolean","description":"Notify on sign-in from an unrecognised device.","example":true},"preferredTwoFactorMethod":{"type":"string","description":"Default second factor.","example":"totp"}}},"example":{"alertOnNewDevice":true,"preferredTwoFactorMethod":"totp"}}}}}},"/profile/security/challenge/send":{"post":{"operationId":"SecurityController_sendChallenge","parameters":[],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Send a security challenge","description":"Sends a two-factor challenge code during sign-in, addressed by the challenge token issued when the password step succeeded.\n\nPublic by necessity — the caller is mid-sign-in and holds no session yet. Rate-limit it: the token is the only thing standing between an attacker and sending repeated codes to a user's phone.\n\n#### Signature\n\n```http\nPOST /profile/security/challenge/send (body) -> The result\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Unauthenticated. Rate-limit per token and per account.\n\n#### Errors\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/challenge/verify`","tags":["Users · Security"],"requestBody":{"description":"The challenge to send.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["challengeToken"],"properties":{"challengeToken":{"type":"string","example":"chl_9k2m4h1p7q"},"method":{"type":"string","description":"Which method to use. Defaults to the preferred one.","example":"sms"}}},"example":{"challengeToken":"chl_9k2m4h1p7q","method":"sms"}}}}}},"/profile/security/challenge/verify":{"post":{"operationId":"SecurityController_verifyChallenge","parameters":[],"responses":{"201":{"description":"Tokens on success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid or expired code — The verification code is wrong or has expired.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Invalid or expired code","path":"/profile/security/challenge/verify","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Verify a security challenge","description":"Completes two-factor sign-in by verifying the code against the challenge token.\n\nSet `trustDevice` to skip future prompts on this device — the same caution as the trust endpoint applies, and it should never default to true on a shared machine.\n\n#### Signature\n\n```http\nPOST /profile/security/challenge/verify (body) -> Tokens on success\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Notes\n\n- Limit attempts per challenge token — an unlimited retry defeats a six-digit code.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_CODE | Invalid or expired code | The verification code is wrong or has expired. | Codes are short-lived. Request a new one rather than retrying an old code. |\n\nPlus the standard platform errors: `429`, `500`.\n\n#### See also\n\n- `POST /profile/security/challenge/send`","tags":["Users · Security"],"requestBody":{"description":"The challenge, the code, and whether to trust the device.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["challengeToken","code"],"properties":{"challengeToken":{"type":"string","example":"chl_9k2m4h1p7q"},"code":{"type":"string","description":"The code, or a backup code.","example":"481625"},"trustDevice":{"type":"boolean","default":false,"description":"Skip future prompts on this device.","example":false}}},"examples":{"standard":{"summary":"Verify a code","value":{"challengeToken":"chl_9k2m4h1p7q","code":"481625"}},"trusted":{"summary":"Verify and trust the device","description":"Only offer this on hardware the user owns.","value":{"challengeToken":"chl_9k2m4h1p7q","code":"481625","trustDevice":true}}}}}}}},"/crm/ai-assistant":{"post":{"operationId":"AIAssistantController_createAssistant","summary":"Create an AI assistant","description":"Creates a configurable AI assistant — who it is, where it listens, what starts it and the tools it may use.\n\n**The tools you grant are real capabilities.** Leaving `tools` out grants **every** tool; send `[]` for an assistant that can talk but never act. An assistant with a tool that assigns tickets or messages customers will do exactly that, unattended, when its trigger fires.\n\n#### Signature\n\n```http\nPOST /crm/ai-assistant (body) -> The created assistant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Notes\n\n- Create it inactive, try it, then activate — an assistant with write tools acts without further confirmation.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ASSISTANT_INVALID | Give it a name. The handle may only use letters, numbers, dashes and underscores. | Every problem found is listed, joined; the body also carries `problems[]`. Checks: title and handle present, handle characters, status/visibility/channel values, tools a list of `{ key }`, each trigger with at least one event, each when…then complete, each knowledge source typed and pointed, each capability with an id. | — |\n| `409` | ASSISTANT_HANDLE_TAKEN | There is already an assistant with the handle \"support-triage\". Choose another. | Another assistant in the org has that `name`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ai-assistant/tools`\n- `POST /crm/ai-assistant/{id}/test`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The assistant to create.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Handle — letters, numbers, dashes and underscores; unique in the org.","example":"support-triage"},"title":{"type":"string","description":"Display name.","example":"Support triage assistant"},"description":{"type":"string","example":"Reads new tickets and assigns them by topic"},"status":{"type":"string","enum":["active","inactive","archived"],"description":"Only an active assistant runs. Default active."},"visibility":{"type":"string","enum":["owner","team","organization","global"],"description":"Who may run it. Only `global` assistants can be run by customers. Default owner."},"personality":{"type":"string","description":"How it speaks."},"behaviorRules":{"type":"array","items":{"type":"string"}},"tools":{"type":"array","description":"Actions it may take, by key from `GET /crm/ai-assistant/tools`. **Omitted = every tool, `[]` = none, a list = only those.** Enforced at execution.","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"crm.tickets.assign"},"description":{"type":"string"},"enabled":{"type":"boolean"}}}},"capabilities":{"type":"array","description":"Built-in capabilities from `GET /crm/ai-assistant/capabilities` (an id, or `{ id, ... }`).","items":{}},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","chat","facebook","instagram","whatsapp","tiktok","twitter","linkedin","voice"]},"description":"Where it listens."},"triggers":{"type":"array","items":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"From `GET /crm/ai-assistant/triggers`; at least one."}},"additionalProperties":true},"description":"What starts it."},"interactionRules":{"type":"array","items":{"type":"object","properties":{"when":{"type":"string"},"then":{"type":"string"}}},"description":"When…then rules; both parts required."},"knowledgeSources":{"type":"array","items":{"type":"object","properties":{"sourceType":{"type":"string"},"reference":{"type":"string"}}},"description":"What it may draw on; each needs a type and something to point at."},"voice":{"type":"string"},"voiceProvider":{"type":"string"},"handoff":{"type":"object","additionalProperties":true},"limits":{"type":"object","additionalProperties":true},"safety":{"type":"object","additionalProperties":true},"permissions":{"type":"object","additionalProperties":true}}},"example":{"name":"support-triage","title":"Support triage assistant","status":"inactive","visibility":"organization","channels":["email"],"tools":[{"key":"crm.tickets.assign"}],"triggers":[{"events":["ticket.created"]}]}}}},"responses":{"201":{"description":"The created assistant","content":{"application/json":{"schema":{"type":"object","description":"An AI assistant configuration (`ai_assistant`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Handle — letters, numbers, dashes and underscores; unique in the org.","example":"support-triage"},"title":{"type":"string","description":"Display name.","example":"Support triage assistant"},"description":{"type":"string","example":"Reads new tickets and assigns them by topic"},"status":{"type":"string","enum":["active","inactive","archived"],"description":"Only an active assistant runs. Default active."},"visibility":{"type":"string","enum":["owner","team","organization","global"],"description":"Who may run it. Only `global` assistants can be run by customers. Default owner."},"personality":{"type":"string","description":"How it speaks."},"behaviorRules":{"type":"array","items":{"type":"string"}},"tools":{"type":"array","description":"Actions it may take, by key from `GET /crm/ai-assistant/tools`. **Omitted = every tool, `[]` = none, a list = only those.** Enforced at execution.","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"crm.tickets.assign"},"description":{"type":"string"},"enabled":{"type":"boolean"}}}},"capabilities":{"type":"array","description":"Built-in capabilities from `GET /crm/ai-assistant/capabilities` (an id, or `{ id, ... }`).","items":{}},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","chat","facebook","instagram","whatsapp","tiktok","twitter","linkedin","voice"]},"description":"Where it listens."},"triggers":{"type":"array","items":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"From `GET /crm/ai-assistant/triggers`; at least one."}},"additionalProperties":true},"description":"What starts it."},"interactionRules":{"type":"array","items":{"type":"object","properties":{"when":{"type":"string"},"then":{"type":"string"}}},"description":"When…then rules; both parts required."},"knowledgeSources":{"type":"array","items":{"type":"object","properties":{"sourceType":{"type":"string"},"reference":{"type":"string"}}},"description":"What it may draw on; each needs a type and something to point at."},"voice":{"type":"string"},"voiceProvider":{"type":"string"},"handoff":{"type":"object","additionalProperties":true},"limits":{"type":"object","additionalProperties":true},"safety":{"type":"object","additionalProperties":true},"permissions":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Give it a name. The handle may only use letters, numbers, dashes and underscores. — Every problem found is listed, joined; the body also carries `problems[]`. Checks: title and handle present, handle characters, status/visibility/channel values, tools a list of `{ key }`, each trigger with at least one event, each when…then complete, each knowledge source typed and pointed, each capability with an id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give it a name. The handle may only use letters, numbers, dashes and underscores.","path":"/crm/ai-assistant","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"There is already an assistant with the handle \"support-triage\". Choose another. — Another assistant in the org has that `name`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"There is already an assistant with the handle \"support-triage\". Choose another.","path":"/crm/ai-assistant","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]},"get":{"operationId":"AIAssistantController_listAssistants","summary":"List AI assistants","description":"The assistants configured for the org.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant (visibility?: string, status?: string) -> { data, count }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ai-assistant/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["active","inactive","archived"]}},{"name":"visibility","required":false,"in":"query","schema":{"type":"string","enum":["owner","team","organization","global"]}}],"responses":{"200":{"description":"{ data, count }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"An AI assistant configuration (`ai_assistant`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Handle — letters, numbers, dashes and underscores; unique in the org.","example":"support-triage"},"title":{"type":"string","description":"Display name.","example":"Support triage assistant"},"description":{"type":"string","example":"Reads new tickets and assigns them by topic"},"status":{"type":"string","enum":["active","inactive","archived"],"description":"Only an active assistant runs. Default active."},"visibility":{"type":"string","enum":["owner","team","organization","global"],"description":"Who may run it. Only `global` assistants can be run by customers. Default owner."},"personality":{"type":"string","description":"How it speaks."},"behaviorRules":{"type":"array","items":{"type":"string"}},"tools":{"type":"array","description":"Actions it may take, by key from `GET /crm/ai-assistant/tools`. **Omitted = every tool, `[]` = none, a list = only those.** Enforced at execution.","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"crm.tickets.assign"},"description":{"type":"string"},"enabled":{"type":"boolean"}}}},"capabilities":{"type":"array","description":"Built-in capabilities from `GET /crm/ai-assistant/capabilities` (an id, or `{ id, ... }`).","items":{}},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","chat","facebook","instagram","whatsapp","tiktok","twitter","linkedin","voice"]},"description":"Where it listens."},"triggers":{"type":"array","items":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"From `GET /crm/ai-assistant/triggers`; at least one."}},"additionalProperties":true},"description":"What starts it."},"interactionRules":{"type":"array","items":{"type":"object","properties":{"when":{"type":"string"},"then":{"type":"string"}}},"description":"When…then rules; both parts required."},"knowledgeSources":{"type":"array","items":{"type":"object","properties":{"sourceType":{"type":"string"},"reference":{"type":"string"}}},"description":"What it may draw on; each needs a type and something to point at."},"voice":{"type":"string"},"voiceProvider":{"type":"string"},"handoff":{"type":"object","additionalProperties":true},"limits":{"type":"object","additionalProperties":true},"safety":{"type":"object","additionalProperties":true},"permissions":{"type":"object","additionalProperties":true}}}}}},"count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/tools":{"get":{"operationId":"AIAssistantController_getAvailableTools","summary":"List available tools","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Available tools","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"],"description":"The tools an assistant can be granted, by `key`. Read this before configuring one — it is the catalogue of what an assistant can do, and each entry is a real action against org data.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant/tools () -> Available tools\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ai-assistant/capabilities`"}},"/crm/ai-assistant/capabilities":{"get":{"operationId":"AIAssistantController_getAvailableCapabilities","summary":"List built-in capabilities","description":"The platform's built-in capabilities an assistant can be given in `capabilities`.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant/capabilities () -> Capabilities\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ai-assistant/tools`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Capabilities","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/triggers":{"get":{"operationId":"AIAssistantController_getAvailableTriggers","summary":"List trigger events","description":"The events that can start an assistant — what goes in `triggers[].events`.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant/triggers () -> Trigger events\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ai-assistant`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Trigger events","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/activities":{"get":{"operationId":"AIAssistantController_getAllAssistantActivities","summary":"Get AI assistant activity","description":"What the org's assistants have been doing, newest first — the audit trail for automated actions.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant/activities (limit?: integer) -> { data, count, orgId }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ai-assistant/{id}/activities`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"{ data, count, orgId }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"count":{"type":"integer"},"orgId":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/{id}":{"get":{"operationId":"AIAssistantController_getAssistant","summary":"Get an AI assistant","description":"One assistant with its full configuration.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant/{id} (id: string) -> The assistant\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ASSISTANT_NOT_FOUND | AI Assistant AST-4821 not found | No assistant in this org has that id. | Check the id against `GET /crm/ai-assistant`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/ai-assistant/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Assistant id.","schema":{"type":"string"},"example":"AST-4821"}],"responses":{"200":{"description":"The assistant","content":{"application/json":{"schema":{"type":"object","description":"An AI assistant configuration (`ai_assistant`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Handle — letters, numbers, dashes and underscores; unique in the org.","example":"support-triage"},"title":{"type":"string","description":"Display name.","example":"Support triage assistant"},"description":{"type":"string","example":"Reads new tickets and assigns them by topic"},"status":{"type":"string","enum":["active","inactive","archived"],"description":"Only an active assistant runs. Default active."},"visibility":{"type":"string","enum":["owner","team","organization","global"],"description":"Who may run it. Only `global` assistants can be run by customers. Default owner."},"personality":{"type":"string","description":"How it speaks."},"behaviorRules":{"type":"array","items":{"type":"string"}},"tools":{"type":"array","description":"Actions it may take, by key from `GET /crm/ai-assistant/tools`. **Omitted = every tool, `[]` = none, a list = only those.** Enforced at execution.","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"crm.tickets.assign"},"description":{"type":"string"},"enabled":{"type":"boolean"}}}},"capabilities":{"type":"array","description":"Built-in capabilities from `GET /crm/ai-assistant/capabilities` (an id, or `{ id, ... }`).","items":{}},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","chat","facebook","instagram","whatsapp","tiktok","twitter","linkedin","voice"]},"description":"Where it listens."},"triggers":{"type":"array","items":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"From `GET /crm/ai-assistant/triggers`; at least one."}},"additionalProperties":true},"description":"What starts it."},"interactionRules":{"type":"array","items":{"type":"object","properties":{"when":{"type":"string"},"then":{"type":"string"}}},"description":"When…then rules; both parts required."},"knowledgeSources":{"type":"array","items":{"type":"object","properties":{"sourceType":{"type":"string"},"reference":{"type":"string"}}},"description":"What it may draw on; each needs a type and something to point at."},"voice":{"type":"string"},"voiceProvider":{"type":"string"},"handoff":{"type":"object","additionalProperties":true},"limits":{"type":"object","additionalProperties":true},"safety":{"type":"object","additionalProperties":true},"permissions":{"type":"object","additionalProperties":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"AI Assistant AST-4821 not found — No assistant in this org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"AI Assistant AST-4821 not found","path":"/crm/ai-assistant/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]},"put":{"operationId":"AIAssistantController_updateAssistant","summary":"Update an AI assistant","description":"Changes the fields sent; the merged result is validated as a whole and refused if it could not run. Changing `tools` or instructions changes what it does on its very next run — there is no staging.\n\n#### Signature\n\n```http\nPUT /crm/ai-assistant/{id} (id: string, body) -> The updated assistant (with `id`)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ASSISTANT_NOT_FOUND | AI Assistant AST-4821 not found | No assistant in this org has that id. | Check the id against `GET /crm/ai-assistant`. |\n| `400` | ASSISTANT_INVALID | Give it a name. The handle may only use letters, numbers, dashes and underscores. | Every problem found is listed, joined; the body also carries `problems[]`. Checks: title and handle present, handle characters, status/visibility/channel values, tools a list of `{ key }`, each trigger with at least one event, each when…then complete, each knowledge source typed and pointed, each capability with an id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ai-assistant/{id}/test`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Assistant id.","schema":{"type":"string"},"example":"AST-4821"}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Handle — letters, numbers, dashes and underscores; unique in the org.","example":"support-triage"},"title":{"type":"string","description":"Display name.","example":"Support triage assistant"},"description":{"type":"string","example":"Reads new tickets and assigns them by topic"},"status":{"type":"string","enum":["active","inactive","archived"],"description":"Only an active assistant runs. Default active."},"visibility":{"type":"string","enum":["owner","team","organization","global"],"description":"Who may run it. Only `global` assistants can be run by customers. Default owner."},"personality":{"type":"string","description":"How it speaks."},"behaviorRules":{"type":"array","items":{"type":"string"}},"tools":{"type":"array","description":"Actions it may take, by key from `GET /crm/ai-assistant/tools`. **Omitted = every tool, `[]` = none, a list = only those.** Enforced at execution.","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"crm.tickets.assign"},"description":{"type":"string"},"enabled":{"type":"boolean"}}}},"capabilities":{"type":"array","description":"Built-in capabilities from `GET /crm/ai-assistant/capabilities` (an id, or `{ id, ... }`).","items":{}},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","chat","facebook","instagram","whatsapp","tiktok","twitter","linkedin","voice"]},"description":"Where it listens."},"triggers":{"type":"array","items":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"From `GET /crm/ai-assistant/triggers`; at least one."}},"additionalProperties":true},"description":"What starts it."},"interactionRules":{"type":"array","items":{"type":"object","properties":{"when":{"type":"string"},"then":{"type":"string"}}},"description":"When…then rules; both parts required."},"knowledgeSources":{"type":"array","items":{"type":"object","properties":{"sourceType":{"type":"string"},"reference":{"type":"string"}}},"description":"What it may draw on; each needs a type and something to point at."},"voice":{"type":"string"},"voiceProvider":{"type":"string"},"handoff":{"type":"object","additionalProperties":true},"limits":{"type":"object","additionalProperties":true},"safety":{"type":"object","additionalProperties":true},"permissions":{"type":"object","additionalProperties":true}}},"example":{"status":"active","behaviorRules":["Assign by topic and set priority from the language used."]}}}},"responses":{"200":{"description":"The updated assistant (with `id`)","content":{"application/json":{"schema":{"type":"object","description":"An AI assistant configuration (`ai_assistant`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","additionalProperties":true,"properties":{"name":{"type":"string","description":"Handle — letters, numbers, dashes and underscores; unique in the org.","example":"support-triage"},"title":{"type":"string","description":"Display name.","example":"Support triage assistant"},"description":{"type":"string","example":"Reads new tickets and assigns them by topic"},"status":{"type":"string","enum":["active","inactive","archived"],"description":"Only an active assistant runs. Default active."},"visibility":{"type":"string","enum":["owner","team","organization","global"],"description":"Who may run it. Only `global` assistants can be run by customers. Default owner."},"personality":{"type":"string","description":"How it speaks."},"behaviorRules":{"type":"array","items":{"type":"string"}},"tools":{"type":"array","description":"Actions it may take, by key from `GET /crm/ai-assistant/tools`. **Omitted = every tool, `[]` = none, a list = only those.** Enforced at execution.","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string","example":"crm.tickets.assign"},"description":{"type":"string"},"enabled":{"type":"boolean"}}}},"capabilities":{"type":"array","description":"Built-in capabilities from `GET /crm/ai-assistant/capabilities` (an id, or `{ id, ... }`).","items":{}},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","chat","facebook","instagram","whatsapp","tiktok","twitter","linkedin","voice"]},"description":"Where it listens."},"triggers":{"type":"array","items":{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"From `GET /crm/ai-assistant/triggers`; at least one."}},"additionalProperties":true},"description":"What starts it."},"interactionRules":{"type":"array","items":{"type":"object","properties":{"when":{"type":"string"},"then":{"type":"string"}}},"description":"When…then rules; both parts required."},"knowledgeSources":{"type":"array","items":{"type":"object","properties":{"sourceType":{"type":"string"},"reference":{"type":"string"}}},"description":"What it may draw on; each needs a type and something to point at."},"voice":{"type":"string"},"voiceProvider":{"type":"string"},"handoff":{"type":"object","additionalProperties":true},"limits":{"type":"object","additionalProperties":true},"safety":{"type":"object","additionalProperties":true},"permissions":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Give it a name. The handle may only use letters, numbers, dashes and underscores. — Every problem found is listed, joined; the body also carries `problems[]`. Checks: title and handle present, handle characters, status/visibility/channel values, tools a list of `{ key }`, each trigger with at least one event, each when…then complete, each knowledge source typed and pointed, each capability with an id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give it a name. The handle may only use letters, numbers, dashes and underscores.","path":"/crm/ai-assistant/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"AI Assistant AST-4821 not found — No assistant in this org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"AI Assistant AST-4821 not found","path":"/crm/ai-assistant/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]},"delete":{"operationId":"AIAssistantController_deleteAssistant","summary":"Delete an AI assistant","description":"Deletes an assistant; it stops running. Setting status `inactive` or `archived` instead keeps the configuration.\n\n#### Signature\n\n```http\nDELETE /crm/ai-assistant/{id} (id: string) -> { success, message }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ASSISTANT_NOT_FOUND | AI Assistant AST-4821 not found | No assistant in this org has that id. | Check the id against `GET /crm/ai-assistant`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `PUT /crm/ai-assistant/{id}`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Assistant id.","schema":{"type":"string"},"example":"AST-4821"}],"responses":{"200":{"description":"{ success, message }","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}},"example":{"success":true,"message":"AI Assistant deleted successfully"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"AI Assistant AST-4821 not found — No assistant in this org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"AI Assistant AST-4821 not found","path":"/crm/ai-assistant/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/{id}/execute":{"post":{"operationId":"AIAssistantController_executeAssistant","summary":"Run an AI assistant","description":"Runs an assistant now and returns the result when it finishes. Staff can run any active assistant; a **customer** can run one only when its visibility is `global`.\n\n**This performs real actions** — any tool the assistant holds may be called: messages sent, records changed.\n\n#### Signature\n\n```http\nPOST /crm/ai-assistant/{id}/execute (id: string, body) -> The run result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Side-effecting. Anything the assistant's tools can do, it may do.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ASSISTANT_NOT_FOUND | AI Assistant AST-4821 not found | No assistant in this org has that id. | Check the id against `GET /crm/ai-assistant`. |\n| `403` | ASSISTANT_NOT_FOR_CUSTOMERS | This assistant is not available to customers | The caller is a customer (or a system user) and the assistant's visibility is not `global`. | — |\n| `400` | ASSISTANT_NOT_ACTIVE | AI Assistant is inactive. Activate this training assistant before running a test. | The assistant's status is not `active`. | Set status to active with `PUT /crm/ai-assistant/{id}`. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ai-assistant/{id}/execute/stream`\n- `POST /crm/ai-assistant/{id}/test`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Assistant id.","schema":{"type":"string"},"example":"AST-4821"}],"requestBody":{"description":"The run.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"type":"string","description":"What to do, or what the person said.","example":"Triage ticket TKT-4821"},"conversationId":{"type":"string","description":"Continue a conversation."},"data":{"type":"object","additionalProperties":true,"description":"Extra context for the run."}}},"example":{"task":"Triage ticket TKT-4821","data":{"ticketId":"TKT-4821"}}}}},"responses":{"201":{"description":"The run result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"AI Assistant is inactive. Activate this training assistant before running a test. — The assistant's status is not `active`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"AI Assistant is inactive. Activate this training assistant before running a test.","path":"/crm/ai-assistant/{id}/execute","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This assistant is not available to customers — The caller is a customer (or a system user) and the assistant's visibility is not `global`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This assistant is not available to customers","path":"/crm/ai-assistant/{id}/execute","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"AI Assistant AST-4821 not found — No assistant in this org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"AI Assistant AST-4821 not found","path":"/crm/ai-assistant/{id}/execute","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/{id}/execute/stream":{"post":{"operationId":"AIAssistantController_executeAssistantStream","summary":"Run an AI assistant, streaming","description":"The streaming form of `execute`: Server-Sent Events, each `data:` line a JSON chunk; the last is `{\"type\":\"done\"}`, or `{\"type\":\"error\",\"error\":\"…\"}` on failure. Same access rules and real side effects as `execute`.\n\nA refusal before the run starts (not found, not active, not for customers) is an ordinary HTTP error, not an event.\n\n#### Signature\n\n```http\nPOST /crm/ai-assistant/{id}/execute/stream (id: string, body) -> A stream of run output\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A dropped connection does not stop the run — the assistant continues acting.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ASSISTANT_NOT_FOUND | AI Assistant AST-4821 not found | No assistant in this org has that id. | Check the id against `GET /crm/ai-assistant`. |\n| `403` | ASSISTANT_NOT_FOR_CUSTOMERS | This assistant is not available to customers | The caller is a customer (or a system user) and the assistant's visibility is not `global`. | — |\n| `400` | ASSISTANT_NOT_ACTIVE | AI Assistant is inactive. Activate this training assistant before running a test. | The assistant's status is not `active`. | Set status to active with `PUT /crm/ai-assistant/{id}`. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ai-assistant/{id}/execute`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Assistant id.","schema":{"type":"string"},"example":"AST-4821"}],"requestBody":{"description":"The run.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"type":"string","description":"What to do, or what the person said.","example":"Triage ticket TKT-4821"},"conversationId":{"type":"string","description":"Continue a conversation."},"data":{"type":"object","additionalProperties":true,"description":"Extra context for the run."}}},"example":{"task":"Triage ticket TKT-4821"}}}},"responses":{"200":{"description":"A stream of run output","content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"AI Assistant is inactive. Activate this training assistant before running a test. — The assistant's status is not `active`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"AI Assistant is inactive. Activate this training assistant before running a test.","path":"/crm/ai-assistant/{id}/execute/stream","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This assistant is not available to customers — The caller is a customer (or a system user) and the assistant's visibility is not `global`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This assistant is not available to customers","path":"/crm/ai-assistant/{id}/execute/stream","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"AI Assistant AST-4821 not found — No assistant in this org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"AI Assistant AST-4821 not found","path":"/crm/ai-assistant/{id}/execute/stream","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/voice/templates":{"get":{"operationId":"AIAssistantController_getVoiceAssistantTemplates","summary":"List voice assistant templates","description":"Pre-configured voice assistant setups for common uses — example bodies for `POST /crm/ai-assistant/voice/setup`.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant/voice/templates () -> { templates, count }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ai-assistant/voice/setup`","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"{ templates, count }","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"type":"object","additionalProperties":true}},"count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/{id}/activities":{"get":{"operationId":"AIAssistantController_getAssistantActivities","summary":"Get one assistant's activity","description":"The activity history for a single assistant — what it ran, when, and what it did.\n\n#### Signature\n\n```http\nGET /crm/ai-assistant/{id}/activities (id: string, limit?: integer) -> { data, count, ... }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ai-assistant/activities`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Assistant id.","schema":{"type":"string"},"example":"AST-4821"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"{ data, count, ... }","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"count":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/{id}/test":{"post":{"operationId":"AIAssistantController_testAssistant","summary":"Try an AI assistant","description":"Runs the assistant once in a throwaway conversation and returns what it replied, so you can see how it responds. **It is a real run, not a dry run:** the tools it holds act on real data. Try it with `tools: []` or on test records first. The assistant must be active.\n\n#### Signature\n\n```http\nPOST /crm/ai-assistant/{id}/test (id: string, body) -> `reply` (what it said, as text) and the full `run`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | ASSISTANT_NOT_FOUND | AI Assistant AST-4821 not found | No assistant in this org has that id. | Check the id against `GET /crm/ai-assistant`. |\n| `403` | ASSISTANT_NOT_FOR_CUSTOMERS | This assistant is not available to customers | The caller is a customer (or a system user) and the assistant's visibility is not `global`. | — |\n| `400` | ASSISTANT_NOT_ACTIVE | AI Assistant is inactive. Activate this training assistant before running a test. | The assistant's status is not `active`. | Set status to active with `PUT /crm/ai-assistant/{id}`. |\n\nPlus the standard platform errors: `401`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/ai-assistant/{id}/execute`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"},{"name":"id","required":true,"in":"path","description":"Assistant id.","schema":{"type":"string"},"example":"AST-4821"}],"requestBody":{"description":"What a person says to it.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["task"],"properties":{"task":{"type":"string"}}},"example":{"task":"Customer says their order never arrived"}}}},"responses":{"201":{"description":"`reply` (what it said, as text) and the full `run`","content":{"application/json":{"schema":{"type":"object","properties":{"reply":{"type":"string"},"run":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"AI Assistant is inactive. Activate this training assistant before running a test. — The assistant's status is not `active`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"AI Assistant is inactive. Activate this training assistant before running a test.","path":"/crm/ai-assistant/{id}/test","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This assistant is not available to customers — The caller is a customer (or a system user) and the assistant's visibility is not `global`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This assistant is not available to customers","path":"/crm/ai-assistant/{id}/test","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"AI Assistant AST-4821 not found — No assistant in this org has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"AI Assistant AST-4821 not found","path":"/crm/ai-assistant/{id}/test","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/crm/ai-assistant/voice/setup":{"post":{"operationId":"AIAssistantController_setupVoiceAgent","summary":"Set up a voice assistant","description":"Creates a voice AI assistant and points a Twilio number at it (an existing number, a specific one, or a newly purchased one in `areaCode`), with the voice and status webhooks configured. It then answers calls itself, so try it before putting a real number on it.\n\n#### Signature\n\n```http\nPOST /crm/ai-assistant/voice/setup (body) -> { assistant, phone, webhooks: { voice, status } }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `Staff (signed-in users, not customers)`.\n\n#### Notes\n\n- A voice assistant talks to callers unsupervised.\n- A Twilio setup failure surfaces as a server error with the reason (\"Failed to setup Twilio: …\").\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ASSISTANT_INVALID | Give it a name. The handle may only use letters, numbers, dashes and underscores. | Every problem found is listed, joined; the body also carries `problems[]`. Checks: title and handle present, handle characters, status/visibility/channel values, tools a list of `{ key }`, each trigger with at least one event, each when…then complete, each knowledge source typed and pointed, each capability with an id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/ai-assistant/voice/templates`","parameters":[{"name":"orgid","required":true,"in":"header","description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"requestBody":{"description":"The voice assistant.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","title"],"properties":{"name":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"personality":{"type":"string"},"behaviorRules":{"type":"array","items":{"type":"string"}},"voiceModel":{"type":"string","enum":["alloy","echo","fable","onyx","nova","shimmer"],"default":"alloy"},"phoneNumberId":{"type":"string","description":"Use an existing number by id."},"phoneNumber":{"type":"string","description":"Use this number.","example":"+15551234567"},"purchaseNew":{"type":"boolean","description":"Buy a new number (billable)."},"areaCode":{"type":"string","example":"415"}}},"example":{"name":"front-desk","title":"Front desk","voiceModel":"nova","purchaseNew":true,"areaCode":"415"}}}},"responses":{"201":{"description":"{ assistant, phone, webhooks: { voice, status } }","content":{"application/json":{"schema":{"type":"object","properties":{"assistant":{"type":"object","additionalProperties":true},"phone":{"type":"object","additionalProperties":true},"webhooks":{"type":"object","properties":{"voice":{"type":"string"},"status":{"type":"string"}}}}}}}},"400":{"description":"Give it a name. The handle may only use letters, numbers, dashes and underscores. — Every problem found is listed, joined; the body also carries `problems[]`. Checks: title and handle present, handle characters, status/visibility/channel values, tools a list of `{ key }`, each trigger with at least one event, each when…then complete, each knowledge source typed and pointed, each capability with an id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give it a name. The handle may only use letters, numbers, dashes and underscores.","path":"/crm/ai-assistant/voice/setup","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · AI"]}},"/business-made/training/ai/course-from-sop":{"post":{"operationId":"ReadinessAiController_courseFromSop","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ course, sources:[{datatype, id, version, title, characters, warning?}], warnings:[] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Choose at least one document or policy… — No sources, bad language code, or quizQuestions out of range.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Choose at least one document or policy…","path":"/business-made/training/ai/course-from-sop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Policy not found — A policyId does not exist.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Policy not found","path":"/business-made/training/ai/course-from-sop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"None of the chosen sources has any readable text. — Every source was empty, a scanned image, or an unsupported file type.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"None of the chosen sources has any readable text.","path":"/business-made/training/ai/course-from-sop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"424":{"description":"No AI integration is set up for this organization. — Neither the org nor the platform has an active AI integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":424,"error":"No AI integration is set up for this organization.","path":"/business-made/training/ai/course-from-sop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"The AI provider did not answer. — The provider call failed after its own retries.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"The AI provider did not answer.","path":"/business-made/training/ai/course-from-sop","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Business Made · Readiness AI"],"summary":"Draft a course from SOPs and policies","description":"Reads the chosen policies (content, sections and attached document) and documents (files in the org’s storage, by fileinfo id or path), and drafts a course with short modules and, by default, a multiple-choice quiz — using only what the sources say. The draft records the version of every source, so a later material change regenerates it. Uses the org’s AI integration (or the platform’s, metered to the org).\n\nThe course is a normal bm_course (`source: own`, `status: draft`, `type: self-paced`). Module text is in `content.modules[].sections[{title, body}]` and `content.modules[].body` (sections joined); the quiz is `assessment.items[{id, question, options[], answerIndex, explanation, moduleId}]` with `assessment.passingScore`. Translations sit beside the text: `content.modules[].translations[lang]` = `{title, description, body, sections}` and `assessment.items[].translations[lang]` = `{question, options, explanation}`. `generation` records `{generator: \"readiness-ai\", lineageId, previousVersionId?, supersededById?, sources:[{datatype: bm_policy|fileinfo|file, id, version, title, path?}], changedSources?, reason: initial|source_changed|manual, audience?, quiz, languages, model, generatedAt, generatedBy, publishedAt?, publishedBy?, warnings}`.\n\n#### Signature\n\n```http\nPOST /business-made/training/ai/course-from-sop (body) -> `{ course, sources:[{datatype, id, version, title, characters, warning?}], warnings:[] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- About 120,000 characters of source text are used; anything cut is named in `warnings`.\n- Translation failures do not fail the request; they appear in `warnings`.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | INVALID_COURSE_REQUEST | Choose at least one document or policy… | No sources, bad language code, or quizQuestions out of range. | `problems` lists each. |\n| `404` | POLICY_NOT_FOUND | Policy not found | A policyId does not exist. | — |\n| `422` | NO_SOURCE_TEXT | None of the chosen sources has any readable text. | Every source was empty, a scanned image, or an unsupported file type. | `warnings` says why for each source. PDF, DOCX, PPTX, TXT, MD and HTML are read. |\n| `424` | AI_NOT_CONFIGURED | No AI integration is set up for this organization. | Neither the org nor the platform has an active AI integration. | Add an AI integration in Integrations. |\n| `502` | AI_PROVIDER_ERROR | The AI provider did not answer. | The provider call failed after its own retries. | Try again; the message carries the provider’s reason. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/training/ai/courses/{id}/publish`\n- `POST /business-made/training/ai/courses/{id}/translate`","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentIds":{"type":"array","items":{"type":"string"},"description":"fileinfo sks or storage paths"},"policyIds":{"type":"array","items":{"type":"string"},"description":"bm_policy sks"},"title":{"type":"string"},"roles":{"type":"array","items":{"type":"string"},"description":"Who the course is for — stored as eligibility.requiredForRoles"},"locations":{"type":"array","items":{"type":"string"},"description":"Stored as generation.audience.locations"},"quiz":{"type":"boolean","default":true},"quizQuestions":{"type":"integer","minimum":1,"maximum":30,"default":8},"languages":{"type":"array","items":{"type":"string"},"description":"Language codes to translate into, e.g. [\"es\", \"zh-Hans\"]"},"guidance":{"type":"string","description":"The owner’s own words on what to emphasise"}}},"example":{"policyIds":["POL-FOOD01"],"documentIds":["sops/closing-checklist.pdf"],"title":"Closing the kitchen","roles":["Cook"],"languages":["es"]}}}}}},"/business-made/training/ai/courses":{"get":{"operationId":"ReadinessAiController_courses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"Only lineages with a version in this status (draft, active, archived)."},{"name":"lineage","required":false,"in":"query","schema":{"type":"string"},"description":"One lineage (the sk of its first version)."}],"responses":{"200":{"description":"`{ data:[{ lineageId, title, live, draft, versions:[{id, version, status, reason, generatedAt, publishedAt, languages, modules, questions, sources, changedSources, warnings}] }], total }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness AI"],"summary":"List generated courses by lineage","description":"One row per course lineage (all versions of one generated course): the live version, the pending draft if any, and every version with its sources, what changed and why it was made.\n\n#### Signature\n\n```http\nGET /business-made/training/ai/courses (status?: string, lineage?: string) -> `{ data:[{ lineageId, title, live, draft, versions:[{id, version, status, reason, generatedAt, publishedAt, languages, modules, questions, sources, changedSources, warnings}] }], total }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/training/ai/courses/{id}/sources":{"get":{"operationId":"ReadinessAiController_sources","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk (any version of a generated course).","example":"CRS-AB12CD"}],"responses":{"200":{"description":"`{ courseId, version, sources:[{datatype, id, title, version, currentVersion, changed}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/ai/courses/{id}/sources","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness AI"],"summary":"Sources of a course and whether they changed","description":"Each source the version was built from, the version it was read at, the version it is at now, and `changed`.\n\n#### Signature\n\n```http\nGET /business-made/training/ai/courses/{id}/sources (id: string) -> `{ courseId, version, sources:[{datatype, id, title, version, currentVersion, changed}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No course has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/training/ai/courses/{id}/regenerate":{"post":{"operationId":"ReadinessAiController_regenerate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk (any version of a generated course).","example":"CRS-AB12CD"}],"responses":{"201":{"description":"`{ course, replacedDraft }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only courses built from SOPs can be regenerated. — The course was not made by course-from-sop.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only courses built from SOPs can be regenerated.","path":"/business-made/training/ai/courses/{id}/regenerate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/ai/courses/{id}/regenerate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"None of the chosen sources has any readable text. — Every source was empty, a scanned image, or an unsupported file type.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"None of the chosen sources has any readable text.","path":"/business-made/training/ai/courses/{id}/regenerate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"424":{"description":"No AI integration is set up for this organization. — Neither the org nor the platform has an active AI integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":424,"error":"No AI integration is set up for this organization.","path":"/business-made/training/ai/courses/{id}/regenerate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"The AI provider did not answer. — The provider call failed after its own retries.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"The AI provider did not answer.","path":"/business-made/training/ai/courses/{id}/regenerate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Business Made · Readiness AI"],"summary":"Regenerate a course from its sources","description":"Rebuilds the course from the current versions of its sources, keeping audience, quiz and languages. If the lineage already has an unpublished draft, that draft is rebuilt in place; otherwise a new draft version is created (version + 1, `previousVersionId` = the live one). This also happens on its own when a source changes materially: a policy saved with a new changeLog entry marked `material`, a replaced policy document, or an updated fileinfo record.\n\n#### Signature\n\n```http\nPOST /business-made/training/ai/courses/{id}/regenerate (id: string) -> `{ course, replacedDraft }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No course has that id. | — |\n| `400` | NOT_GENERATED | Only courses built from SOPs can be regenerated. | The course was not made by course-from-sop. | Edit or publish it from Learning instead. |\n| `422` | NO_SOURCE_TEXT | None of the chosen sources has any readable text. | Every source was empty, a scanned image, or an unsupported file type. | `warnings` says why for each source. PDF, DOCX, PPTX, TXT, MD and HTML are read. |\n| `424` | AI_NOT_CONFIGURED | No AI integration is set up for this organization. | Neither the org nor the platform has an active AI integration. | Add an AI integration in Integrations. |\n| `502` | AI_PROVIDER_ERROR | The AI provider did not answer. | The provider call failed after its own retries. | Try again; the message carries the provider’s reason. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/training/ai/check-sources`"}},"/business-made/training/ai/courses/{id}/translate":{"post":{"operationId":"ReadinessAiController_translate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk (any version of a generated course).","example":"CRS-AB12CD"}],"responses":{"201":{"description":"`{ course, languages, warnings }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Not a language code — Empty list or a value that is not a language code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Not a language code","path":"/business-made/training/ai/courses/{id}/translate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/ai/courses/{id}/translate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"424":{"description":"No AI integration is set up for this organization. — Neither the org nor the platform has an active AI integration.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":424,"error":"No AI integration is set up for this organization.","path":"/business-made/training/ai/courses/{id}/translate","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness AI"],"summary":"Translate a course","description":"Adds (or replaces) translations of the modules and quiz for each language. The languages are remembered, so regenerated versions are translated too.\n\n#### Signature\n\n```http\nPOST /business-made/training/ai/courses/{id}/translate (id: string, body) -> `{ course, languages, warnings }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No course has that id. | — |\n| `400` | INVALID_LANGUAGES | Not a language code | Empty list or a value that is not a language code. | — |\n| `424` | AI_NOT_CONFIGURED | No AI integration is set up for this organization. | Neither the org nor the platform has an active AI integration. | Add an AI integration in Integrations. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"languages":{"type":"array","items":{"type":"string"}}},"required":["languages"]},"example":{"languages":["es","vi"]}}}}}},"/business-made/training/ai/courses/{id}/publish":{"post":{"operationId":"ReadinessAiController_publish","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk (any version of a generated course).","example":"CRS-AB12CD"}],"responses":{"201":{"description":"`{ course, superseded:[ids], rulesMoved:[{id,title}], rulesUsingCourse:[{id,title,status}], requiz:{enrollmentsCreated, enrollmentsMoved, byRule:[{ruleId,title,covered,enrollmentsCreated,enrollmentsMoved,dueDate}]}, reacknowledge:{acknowledgementsCreated, byRule:[{ruleId,title,policyId,policyVersion,covered,acknowledgementsCreated,dueDate}]} }`. `rulesUsingCourse` empty = nobody is required to take it yet.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only courses built from SOPs can be regenerated. — The course was not made by course-from-sop.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only courses built from SOPs can be regenerated.","path":"/business-made/training/ai/courses/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/ai/courses/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This version is not a draft. — It is already live or archived.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This version is not a draft.","path":"/business-made/training/ai/courses/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"The course has no modules to publish. — The draft has no module content.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"The course has no modules to publish.","path":"/business-made/training/ai/courses/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"The readiness engine is not loaded. — Moving rules and enrollments needs the readiness engine, and it is not running in this deployment.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"The readiness engine is not loaded.","path":"/business-made/training/ai/courses/{id}/publish","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Business Made · Readiness AI"],"summary":"Publish a generated course version","description":"Makes the draft live. When it replaces a live version: the old version is archived (its completions stand), every requirement rule that named the old version now names this one, and only the people those rules cover (roles, locations and the rest of each rule’s scope) are asked to redo it — a new enrollment cycle for those who had completed it, open enrollments moved to the new version. For each source policy whose version changed, people covered by that policy’s rules who acknowledged an older version get a pending acknowledgement of the current one. People who were never assigned are left to the readiness sync. The affected rules are then re-synced.\n\n#### Signature\n\n```http\nPOST /business-made/training/ai/courses/{id}/publish (id: string, body) -> `{ course, superseded:[ids], rulesMoved:[{id,title}], rulesUsingCourse:[{id,title,status}], requiz:{enrollmentsCreated, enrollmentsMoved, byRule:[{ruleId,title,covered,enrollmentsCreated,enrollmentsMoved,dueDate}]}, reacknowledge:{acknowledgementsCreated, byRule:[{ruleId,title,policyId,policyVersion,covered,acknowledgementsCreated,dueDate}]} }`. `rulesUsingCourse` empty = nobody is required to take it yet.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No course has that id. | — |\n| `400` | NOT_GENERATED | Only courses built from SOPs can be regenerated. | The course was not made by course-from-sop. | Edit or publish it from Learning instead. |\n| `409` | NOT_DRAFT | This version is not a draft. | It is already live or archived. | — |\n| `422` | COURSE_EMPTY | The course has no modules to publish. | The draft has no module content. | — |\n| `503` | READINESS_UNAVAILABLE | The readiness engine is not loaded. | Moving rules and enrollments needs the readiness engine, and it is not running in this deployment. | Retry later. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/readiness/rules`\n- `POST /business-made/readiness/sync`","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"dueWithinDays":{"type":"integer","minimum":0,"description":"Days to redo. Default: the rule’s due.dueWithinDays, then the policy’s acknowledgement.deadlineDays, then 14."}}}}}}}},"/business-made/training/ai/check-sources":{"post":{"operationId":"ReadinessAiController_checkSources","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ checked, regenerated:[{lineageId, from, draft, staleSources}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness AI"],"summary":"Regenerate courses whose sources changed","description":"Files can change without any record being saved (a new upload to the same path), so this compares every generated course’s recorded source versions with the current ones and makes a new draft for each stale course. Pass `courseId` to check one.\n\n#### Signature\n\n```http\nPOST /business-made/training/ai/check-sources (body) -> `{ checked, regenerated:[{lineageId, from, draft, staleSources}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"courseId":{"type":"string"}}}}}}}},"/business-made/training/ai/new-hire":{"get":{"operationId":"ReadinessAiController_newHireState","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employee","required":false,"in":"query","schema":{"type":"string"},"description":"Employee badge code or bm_employee sk."},{"name":"email","required":false,"in":"query","schema":{"type":"string"},"description":"The person’s email (as seen in the conversation)."}],"responses":{"200":{"description":"`{ employee:{id, name, email, location, position, department, startDate}, onboarding: <GET staff-portal/onboarding shape> | null, training: {items:[...]} (GET staff-portal/training shape), summary:{ onboarding:{done,total,overdue}, training:{total, done, overdue, dueSoon}, next:[{kind, title, dueDate, status}], text } }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You can only see your own onboarding. — A person who is not an admin or AI employee named someone else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You can only see your own onboarding.","path":"/business-made/training/ai/new-hire","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Employee not found — No employee matches, or the caller has no employee record.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/training/ai/new-hire","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Readiness AI"],"summary":"A new hire’s onboarding and training state","description":"Everything a new hire might ask about: their onboarding plan (stages, tasks, what others are doing for them) and required training, with counts of overdue and due-soon items and the next things due — computed on the server. Without `employee`/`email` it is the caller’s own. Naming someone else needs an admin role or an AI employee identity; this is how an AI employee answers a new hire who messages it.\n\n#### Signature\n\n```http\nGET /business-made/training/ai/new-hire (employee?: string, email?: string) -> `{ employee:{id, name, email, location, position, department, startDate}, onboarding: <GET staff-portal/onboarding shape> \\| null, training: {items:[...]} (GET staff-portal/training shape), summary:{ onboarding:{done,total,overdue}, training:{total, done, overdue, dueSoon}, next:[{kind, title, dueDate, status}], text } }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | NOT_ALLOWED | You can only see your own onboarding. | A person who is not an admin or AI employee named someone else. | — |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee matches, or the caller has no employee record. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/queue-manager/list-queues":{"get":{"operationId":"QueueManagerController_listQueues","parameters":[],"responses":{"200":{"description":"Queues","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List queues","description":"Every Bull queue the platform runs. Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Signature\n\n```http\nGET /queue-manager/list-queues () -> Queues\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/list-stats`","tags":["Sync · Queues"]}},"/queue-manager/list-stats":{"get":{"operationId":"QueueManagerController_getQueueStats","parameters":[],"responses":{"200":{"description":"Per-queue statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get queue statistics","description":"Job counts by state across all queues — waiting, active, completed, failed. The first place to look when work is not being processed.\n\n#### Signature\n\n```http\nGET /queue-manager/list-stats () -> Per-queue statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/details/{queueName}`","tags":["Sync · Queues"]}},"/queue-manager/details/{queueName}":{"get":{"operationId":"QueueManagerController_getQueueDetails","parameters":[{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"200":{"description":"The queue","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get queue details","description":"One queue with its jobs and state.\n\n#### Signature\n\n```http\nGET /queue-manager/details/{queueName} (queueName: string) -> The queue\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /queue-manager/action/{queueName}`","tags":["Sync · Queues"]}},"/queue-manager/action/{queueName}":{"post":{"operationId":"QueueManagerController_performQueueAction","parameters":[{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Perform a queue action","description":"Runs an administrative action on a queue — pause, resume, drain, clean, retry failed jobs.\n\nSeveral of these are destructive: draining or cleaning a queue **discards queued work permanently**, and the queue is shared across tenants, so the jobs discarded may belong to other orgs. Read the queue's stats first.\n\n#### Signature\n\n```http\nPOST /queue-manager/action/{queueName} (queueName: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n- Drain and clean discard queued jobs permanently.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/list-stats`","tags":["Sync · Queues"],"requestBody":{"description":"The action.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","description":"e.g. `pause`, `resume`, `drain`, `clean`, `retry`.","example":"pause"}}},"example":{"action":"pause"}}}}}},"/queue-manager/queue/{queueName}":{"post":{"operationId":"QueueManagerController_queueJob","parameters":[{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"201":{"description":"The queued job","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Queue a job","description":"Puts a job directly onto a named queue, bypassing whatever normally produces it. Useful for replaying work; the payload is not validated against what the consumer expects, so a malformed job fails at processing time rather than here.\n\n#### Signature\n\n```http\nPOST /queue-manager/queue/{queueName} (queueName: string, body) -> The queued job\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n- Payload is not validated against the consumer.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/details/{queueName}`","tags":["Sync · Queues"],"requestBody":{"description":"The job payload.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"sync-products","data":{"orgId":"org_4821","platform":"shopify"}}}}}}},"/queue-manager/sync-ticks/remove":{"post":{"operationId":"QueueManagerController_removeSyncTicks","parameters":[],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Remove sync ticks","description":"Removes sync tick entries. **This stops scheduled syncs from firing** — across tenants, since the ticks are shared infrastructure. Removing them silently leaves orgs with stale data rather than producing an error anyone will see.\n\n#### Signature\n\n```http\nPOST /queue-manager/sync-ticks/remove () -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n- Silently stops scheduled syncs.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/sync-ticks`","tags":["Sync · Queues"]}},"/queue-manager/sync-ticks":{"get":{"operationId":"QueueManagerController_listSyncTicks","parameters":[],"responses":{"200":{"description":"Sync ticks","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List sync ticks","description":"The recurring sync tick entries that drive scheduled syncs.\n\n#### Signature\n\n```http\nGET /queue-manager/sync-ticks () -> Sync ticks\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /queue-manager/sync-ticks/remove`","tags":["Sync · Queues"]}},"/queue-manager/schedules/{queueName}":{"get":{"operationId":"QueueManagerController_listSchedules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"200":{"description":"Schedules","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List a queue's schedules","description":"The repeatable schedules attached to a queue.\n\n#### Signature\n\n```http\nGET /queue-manager/schedules/{queueName} (queueName: string) -> Schedules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /queue-manager/schedule/{queueName}`","tags":["Sync · Queues"]}},"/queue-manager/schedule/oneoff/{queueName}":{"post":{"operationId":"QueueManagerController_scheduleOneOffJob","parameters":[{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"201":{"description":"The schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a one-off schedule","description":"Schedules a single future run rather than a recurring one — for a deferred job that should happen once.\n\n#### Signature\n\n```http\nPOST /queue-manager/schedule/oneoff/{queueName} (queueName: string, body) -> The schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /queue-manager/schedule/{queueName}`","tags":["Sync · Queues"],"requestBody":{"description":"The one-off schedule.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"runAt":"2026-09-01T02:00:00.000Z","data":{}}}}}}},"/queue-manager/schedule/{queueName}":{"post":{"operationId":"QueueManagerController_createSchedule","parameters":[{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"201":{"description":"The schedule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a schedule","description":"Adds a recurring schedule to a queue. A too-frequent cron here multiplies work across every org on the queue, so check the expression before creating it.\n\n#### Signature\n\n```http\nPOST /queue-manager/schedule/{queueName} (queueName: string, body) -> The schedule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /queue-manager/schedule/pause/{queueName}`","tags":["Sync · Queues"],"requestBody":{"description":"The schedule.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"nightly-sync","cron":"0 2 * * *","data":{}}}}}}},"/queue-manager/schedule/pause/{queueName}":{"post":{"operationId":"QueueManagerController_pauseSchedule","parameters":[{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Pause a schedule","description":"Stops a schedule firing. Runs missed while paused are not made up when it resumes — the schedule simply resumes from the next occurrence.\n\n#### Signature\n\n```http\nPOST /queue-manager/schedule/pause/{queueName} (queueName: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n- Missed runs are not backfilled on resume.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /queue-manager/schedule/resume/{queueName}`","tags":["Sync · Queues"],"requestBody":{"description":"Which schedule.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"nightly-sync"}}}}}},"/queue-manager/schedule/resume/{queueName}":{"post":{"operationId":"QueueManagerController_resumeSchedule","parameters":[{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Resume a schedule","description":"Restarts a paused schedule from its next occurrence.\n\n#### Signature\n\n```http\nPOST /queue-manager/schedule/resume/{queueName} (queueName: string, body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `ConfigAdmin`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /queue-manager/schedule/pause/{queueName}`","tags":["Sync · Queues"],"requestBody":{"description":"Which schedule.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"name":"nightly-sync"}}}}}},"/queue-manager/org-schedules/{queueName}":{"get":{"operationId":"QueueManagerController_getOrgSchedules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"queueName","required":true,"in":"path","schema":{"type":"string"},"description":"Queue name.","example":"sync"}],"responses":{"200":{"description":"Schedules","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List the org's schedules on a queue","description":"The calling org's schedules on one queue.\n\n#### Signature\n\n```http\nGET /queue-manager/org-schedules/{queueName} (queueName: string) -> Schedules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/org-schedules`","tags":["Sync · Queues"]}},"/queue-manager/org-schedules":{"get":{"operationId":"QueueManagerController_getAllOrgSchedules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Schedules","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List the org's schedules","description":"Schedules belonging to the calling org — unlike most of this controller, scoped to one tenant.\n\n#### Signature\n\n```http\nGET /queue-manager/org-schedules () -> Schedules\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/org-schedules/{queueName}`","tags":["Sync · Queues"]}},"/queue-manager/schedule-runs/{scheduleId}":{"get":{"operationId":"QueueManagerController_getScheduleRuns","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"scheduleId","required":true,"in":"path","schema":{"type":"string"},"description":"Schedule id. Optional.","example":"SCH-12"},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"example":50},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"failed"},{"name":"type","required":false,"in":"query","schema":{"type":"string"},"example":"sync"},{"name":"scheduleName","required":false,"in":"query","schema":{"type":"string"},"example":"nightly-sync"},{"name":"grouped","required":false,"in":"query","schema":{"type":"boolean"},"description":"Group runs by schedule.","example":false}],"responses":{"200":{"description":"Schedule runs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List schedule runs","description":"Execution history for schedules. `scheduleId` is optional — omit it for runs across all schedules. `grouped=true` collapses the runs by schedule instead of listing them flat.\n\n#### Signature\n\n```http\nGET /queue-manager/schedule-runs/{scheduleId} (scheduleId: string, limit?: integer, status?: string, type?: string, scheduleName?: string, grouped?: boolean) -> Schedule runs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/schedule-stats/{scheduleId}`","tags":["Sync · Queues"]}},"/queue-manager/schedule-stats/{scheduleId}":{"get":{"operationId":"QueueManagerController_getScheduleStats","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"}},{"name":"scheduleId","required":true,"in":"path","schema":{"type":"string"},"description":"Schedule id.","example":"SCH-12"}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get schedule statistics","description":"Success rate, duration and failure counts for one schedule.\n\n#### Signature\n\n```http\nGET /queue-manager/schedule-stats/{scheduleId} (scheduleId: string) -> Statistics\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/schedule-runs/{scheduleId}`","tags":["Sync · Queues"]}},"/queue-manager/debug/delayed-jobs":{"get":{"operationId":"QueueManagerController_getDelayedJobs","parameters":[],"responses":{"200":{"description":"Delayed jobs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List delayed jobs (debug)","description":"Jobs sitting in the delayed state across queues — the diagnostic for work that was scheduled but never became active.\n\n#### Signature\n\n```http\nGET /queue-manager/debug/delayed-jobs () -> Delayed jobs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`, `RootSystem`.\n\n#### Notes\n\n- Infrastructure route — acts on a queue shared by every tenant, not just the calling org.\n- Diagnostic endpoint.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /queue-manager/list-stats`","tags":["Sync · Queues"]}},"/crm/auto-campaign/sell-like-mad":{"post":{"operationId":"AutoCampaignController_generateSellLikeMad","summary":"Generate a campaign automatically","description":"Generates a complete multi-platform ad campaign from a brief — copy, creative and targeting — and returns it as a **preview**.\n\nNothing is published and no money is spent at this point. The preview is reviewed, regenerated or edited, and only becomes a real campaign when approved.\n\n#### Signature\n\n```http\nPOST /crm/auto-campaign/sell-like-mad (body) -> The generated campaign preview — per-platform creative plus predictions (reach, clicks, CPC, and `projectedRoas` / `averageOrderValue` from the promoted products' prices, null when prices are unknown)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Generates only. Nothing runs until `POST /crm/auto-campaign/approve`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/auto-campaign/preview/{previewId}`\n- `POST /crm/auto-campaign/approve`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The generated campaign preview — per-platform creative plus predictions (reach, clicks, CPC, and `projectedRoas` / `averageOrderValue` from the promoted products' prices, null when prices are unknown)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid request"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No products found"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"requestBody":{"description":"The brief to generate from.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["totalBudget","budgetType","durationDays"],"properties":{"productIds":{"type":"array","items":{"type":"string"},"description":"Products to promote."},"categorySlug":{"type":"string","description":"Or promote every product in this category."},"totalBudget":{"type":"number","description":"Budget in dollars.","example":50},"budgetType":{"type":"string","enum":["daily","lifetime"],"description":"A daily budget is spent every day of the run; a lifetime budget once."},"durationDays":{"type":"integer","example":14},"platforms":{"type":"array","items":{"type":"string"},"description":"Defaults to every connected platform.","example":["facebook","instagram"]},"objective":{"type":"string","enum":["AWARENESS","TRAFFIC","ENGAGEMENT","LEADS","CONVERSIONS","SALES"]},"targeting":{"type":"object","properties":{"ageRange":{"type":"object","properties":{"min":{"type":"integer"},"max":{"type":"integer"}}},"gender":{"type":"string","enum":["all","male","female"]},"locations":{"type":"array","items":{"type":"string"}},"interests":{"type":"array","items":{"type":"string"}},"excludeAudiences":{"type":"array","items":{"type":"string"}}}},"brandVoice":{"type":"string","enum":["professional","casual","luxury","playful","urgent"]},"customInstructions":{"type":"string"},"campaignName":{"type":"string","description":"Generated when not given."}}},"example":{"productIds":["PRD-4821"],"totalBudget":50,"budgetType":"daily","durationDays":14,"platforms":["facebook","instagram"],"objective":"SALES","brandVoice":"casual"}}}}}},"/crm/auto-campaign/preview/{previewId}":{"get":{"operationId":"AutoCampaignController_getPreview","summary":"Get a campaign preview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"previewId","required":true,"in":"path","description":"Campaign preview id.","schema":{"type":"string"},"example":"PRV-4821"}],"responses":{"200":{"description":"The campaign preview","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Preview not found — No preview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Preview not found","path":"/crm/auto-campaign/preview/{previewId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"description":"Fetches a generated preview — the copy, creative and targeting proposed for each platform, before anything is published.\n\n#### Signature\n\n```http\nGET /crm/auto-campaign/preview/{previewId} (previewId: string) -> The campaign preview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Preview not found | No preview has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/auto-campaign/approve`"},"delete":{"operationId":"AutoCampaignController_discardPreview","summary":"Discard a campaign preview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"previewId","required":true,"in":"path","description":"Campaign preview id.","schema":{"type":"string"},"example":"PRV-4821"}],"responses":{"200":{"description":"Deletion result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Preview not found — No preview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Preview not found","path":"/crm/auto-campaign/preview/{previewId}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"description":"Deletes an unapproved preview. Nothing was published, so nothing is withdrawn.\n\n#### Signature\n\n```http\nDELETE /crm/auto-campaign/preview/{previewId} (previewId: string) -> Deletion result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Preview not found | No preview has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/auto-campaign/sell-like-mad`"}},"/crm/auto-campaign/approve":{"post":{"operationId":"AutoCampaignController_approveCampaign","summary":"Approve and launch a generated campaign","description":"Turns an approved preview into a real campaign and launches it.\n\n**This is the point money starts being spent.** Everything before it is generation and review; this publishes to the ad platforms against the budget in the brief.\n\n#### Signature\n\n```http\nPOST /crm/auto-campaign/approve (body) -> The launched campaign\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Starts real ad spend. Review every platform variation before approving.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Preview not found | No preview has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/auto-campaign/preview/{previewId}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The launched campaign","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Preview not found — No preview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Preview not found","path":"/crm/auto-campaign/approve","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"410":{"description":"Preview expired"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"requestBody":{"description":"Which preview to approve, with any last changes.","required":true,"content":{"application/json":{"schema":{"type":"object","required":["previewId"],"properties":{"previewId":{"type":"string","example":"PRV-4821"},"modifications":{"type":"array","description":"Per-platform overrides.","items":{"type":"object","required":["platform"],"properties":{"platform":{"type":"string"},"budget":{"type":"number"},"headlines":{"type":"array","items":{"type":"string"}},"descriptions":{"type":"array","items":{"type":"string"}},"cta":{"type":"string"},"enabled":{"type":"boolean","description":"false leaves that platform out."}}}},"scheduleStartDate":{"type":"string","format":"date-time"}}},"example":{"previewId":"PRV-4821","modifications":[{"platform":"instagram","enabled":false}]}}}}}},"/crm/auto-campaign/preview/{previewId}/regenerate/{platform}":{"post":{"operationId":"AutoCampaignController_regeneratePlatformAds","summary":"Regenerate one platform's creative","description":"Regenerates the copy and creative for a single platform within a preview, leaving the others as they are — for when one platform's variation misses and the rest are fine.\n\n#### Signature\n\n```http\nPOST /crm/auto-campaign/preview/{previewId}/regenerate/{platform} (previewId: string, platform: string, body) -> The regenerated platform variation\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Preview not found | No preview has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/auto-campaign/preview/{previewId}/variations/{platform}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"previewId","required":true,"in":"path","description":"Campaign preview id.","schema":{"type":"string"},"example":"PRV-4821"},{"name":"platform","required":true,"in":"path","description":"Ad platform.","schema":{"type":"string"},"example":"facebook"}],"responses":{"200":{"description":"Ads regenerated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformCampaignPreviewDto"}}}},"201":{"description":"The regenerated platform variation","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Preview not found — No preview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Preview not found","path":"/crm/auto-campaign/preview/{previewId}/regenerate/{platform}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"requestBody":{"description":"Optional guidance for the regeneration.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"guidance":"Make it shorter and lead with the discount"}}}}}},"/crm/auto-campaign/platforms":{"get":{"operationId":"AutoCampaignController_getAvailablePlatforms","summary":"List auto-campaign platforms","description":"The platforms auto-generated campaigns can target.\n\n#### Signature\n\n```http\nGET /crm/auto-campaign/platforms () -> Available platforms\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/auto-campaign/sell-like-mad`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Available platforms","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"]}},"/crm/auto-campaign/ai/audience-suggestions":{"post":{"operationId":"AutoCampaignController_getAudienceSuggestions","summary":"Get AI audience suggestions","description":"Suggests audiences to target for a campaign brief. Suggestions only — nothing is created, and each still needs reviewing against your own data.\n\n#### Signature\n\n```http\nPOST /crm/auto-campaign/ai/audience-suggestions (body) -> Suggested audiences\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/marketing/audiences/estimate-reach`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Suggested audiences","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No products found"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"requestBody":{"description":"The brief to suggest audiences for.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"product":"Cola 330ml multipack","goal":"conversions"}}}}}},"/crm/auto-campaign/preview/{previewId}/variations/{platform}":{"post":{"operationId":"AutoCampaignController_getCopyVariations","summary":"Generate creative variations","description":"Produces alternative creatives for one platform, so several can be compared — or tested against each other — before approval.\n\n#### Signature\n\n```http\nPOST /crm/auto-campaign/preview/{previewId}/variations/{platform} (previewId: string, platform: string, body) -> The generated variations\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Preview not found | No preview has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/auto-campaign/preview/{previewId}/predictions/{platform}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"previewId","required":true,"in":"path","description":"Campaign preview id.","schema":{"type":"string"},"example":"PRV-4821"},{"name":"platform","required":true,"in":"path","description":"Ad platform.","schema":{"type":"string"},"example":"facebook"}],"responses":{"201":{"description":"The generated variations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Preview not found — No preview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Preview not found","path":"/crm/auto-campaign/preview/{previewId}/variations/{platform}","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"requestBody":{"description":"How many variations, and any guidance.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"count":3}}}}}},"/crm/auto-campaign/preview/{previewId}/predictions/{platform}":{"get":{"operationId":"AutoCampaignController_getPerformancePredictions","summary":"Get performance predictions","description":"Predicted performance for a platform's creative before it runs — estimated reach and engagement.\n\nThese are model estimates, not commitments. Treat them as a way to compare variations against each other, not as a forecast of actual results.\n\n#### Signature\n\n```http\nGET /crm/auto-campaign/preview/{previewId}/predictions/{platform} (previewId: string, platform: string) -> Predicted performance — impressions, clicks, CPC and, when product prices are known, `projectedRoas: { min, max }` and `averageOrderValue`. A daily budget counts as spend on every day of the run.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Estimates only — useful for ranking variations, not for budgeting.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_FOUND | Preview not found | No preview has that id. | Check the id against the corresponding list endpoint. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/auto-campaign/preview/{previewId}/variations/{platform}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"previewId","required":true,"in":"path","description":"Campaign preview id.","schema":{"type":"string"},"example":"PRV-4821"},{"name":"platform","required":true,"in":"path","description":"Ad platform.","schema":{"type":"string"},"example":"facebook"}],"responses":{"200":{"description":"Predicted performance — impressions, clicks, CPC and, when product prices are known, `projectedRoas: { min, max }` and `averageOrderValue`. A daily budget counts as spend on every day of the run.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Preview not found — No preview has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Preview not found","path":"/crm/auto-campaign/preview/{previewId}/predictions/{platform}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"]}},"/crm/auto-campaign/ai/generate-images":{"post":{"operationId":"AutoCampaignController_generateAdImages","summary":"Generate campaign images","description":"Generates ad imagery from a prompt. Generated images may still need rights and brand review before use — this does not check either.\n\n#### Signature\n\n```http\nPOST /crm/auto-campaign/ai/generate-images (body) -> The generated images\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Review generated imagery before publishing — nothing here checks brand or likeness rights.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /crm/auto-campaign/preview/{previewId}/variations/{platform}`","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The generated images","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"No products found"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"requestBody":{"description":"What to generate.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"prompt":"A chilled cola can on ice, summer sunlight","count":3}}}}}},"/crm/auto-campaign/health":{"get":{"operationId":"AutoCampaignController_healthCheck","summary":"Get auto-campaign health","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Pipeline health","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["CRM · Auto-campaign"],"description":"Whether the generation and publishing pipeline is working. Check this first when generation fails.\n\n#### Signature\n\n```http\nGET /crm/auto-campaign/health () -> Pipeline health\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /crm/auto-campaign/platforms`"}},"/business-made/books/account-map":{"get":{"operationId":"PostingController_getAccountMap","summary":"Get the account map","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The account map","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"How business events map to ledger accounts — which account a sale, refund or payment posts to. This mapping is what makes automatic posting possible.\n\n#### Signature\n\n```http\nGET /business-made/books/account-map () -> The account map\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/account-map`"},"post":{"operationId":"PostingController_setAccountMap","summary":"Update the account map","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The updated map","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Changes how business events post to accounts. **Applies to future postings only** — historical entries keep the accounts they were posted to, so a mapping change makes period-over-period comparisons discontinuous.\n\n#### Signature\n\n```http\nPOST /business-made/books/account-map (body) -> The updated map\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Existing postings are not remapped.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/backfill`","requestBody":{"description":"The mapping to store.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sale":{"revenue":"4000","tax":"2200"},"refund":{"revenue":"4000"}}}}}}},"/business-made/books/backfill":{"post":{"operationId":"PostingController_backfill","summary":"Backfill ledger postings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The backfill result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Generates ledger postings for historical transactions that were never posted — the recovery path after a mapping was missing or the posting pipeline was down.\n\nIt writes real journal entries across a date range, so it moves historical reports. Check the trial balance before and after.\n\n#### Signature\n\n```http\nPOST /business-made/books/backfill (body) -> The backfill result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Re-running over a range that already posted will double-count. Verify coverage first.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/balance-drift`","requestBody":{"description":"What to backfill.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"from":"2026-08-01","to":"2026-08-31"}}}}}},"/business-made/books/backfill-cogs":{"post":{"operationId":"PostingController_backfillCogs","summary":"Backfill cost of goods sold","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The backfill result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Generates missing cost-of-goods postings for historical sales — the counterpart to revenue backfill, for when margin is understated because cost was never posted.\n\n#### Signature\n\n```http\nPOST /business-made/books/backfill-cogs (body) -> The backfill result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Same double-count risk as `backfill` — check what is already posted.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/backfill`","requestBody":{"description":"What to backfill.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"from":"2026-08-01","to":"2026-08-31"}}}}}},"/business-made/books/resume-postings":{"post":{"operationId":"PostingController_resumePostings","summary":"Resume postings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Restarts the automatic posting pipeline after it was stopped or stalled. Check `balance-drift` afterwards to confirm nothing was missed while it was down.\n\n#### Signature\n\n```http\nPOST /business-made/books/resume-postings (body) -> The result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/balance-drift`","requestBody":{"description":"Optional options.","required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{}}}}}},"/business-made/books/post/sale":{"post":{"operationId":"PostingController_postSale","summary":"Post a sale","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The posting result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Posts a sale to the ledger using the account map. Normally driven automatically — call it directly only to record something the pipeline missed.\n\n#### Signature\n\n```http\nPOST /business-made/books/post/sale (body) -> The posting result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Not idempotent — posting the same sale twice double-counts revenue.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/post/refund`","requestBody":{"description":"The sale to post.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"orderNumber":"A7K2M9QX4","amount":129.99,"tax":10.4,"date":"2026-09-15"}}}}}},"/business-made/books/post/refund":{"post":{"operationId":"PostingController_postRefund","summary":"Post a refund","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The posting result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Posts a refund to the ledger, reversing the revenue recognised on the original sale.\n\n#### Signature\n\n```http\nPOST /business-made/books/post/refund (body) -> The posting result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/post/sale`","requestBody":{"description":"The refund to post.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"orderNumber":"A7K2M9QX4","amount":129.99,"date":"2026-09-20"}}}}}},"/business-made/books/post/invoice":{"post":{"operationId":"PostingController_postInvoice","summary":"Post an invoice","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The posting result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Posts an invoice to the ledger, recognising receivable and revenue.\n\n#### Signature\n\n```http\nPOST /business-made/books/post/invoice (body) -> The posting result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/post/payment`","requestBody":{"description":"The invoice to post.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"invoiceNumber":"INV-4821","amount":4200,"date":"2026-09-01"}}}}}},"/business-made/books/post/payment":{"post":{"operationId":"PostingController_postPayment","summary":"Post a payment","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The posting result","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Posts a payment received, clearing the receivable raised by the invoice.\n\n#### Signature\n\n```http\nPOST /business-made/books/post/payment (body) -> The posting result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/ar`","requestBody":{"description":"The payment to post.","required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"invoiceNumber":"INV-4821","amount":4200,"date":"2026-09-25"}}}}}},"/business-made/books/balance-drift":{"get":{"operationId":"PostingController_balanceDrift","summary":"Check for balance drift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Drifted accounts","content":{"application/json":{"schema":{"type":"object","properties":{"scanned":{"type":"number"},"drifted":{"type":"number"},"accounts":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"accountCode":{"type":"string"},"accountName":{"type":"string"},"cached":{"type":"number"},"derived":{"type":"number"},"difference":{"type":"number"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Compares each ledger account's cached balance with the balance its posted journal entries imply, and lists the accounts where they differ (beyond a small tolerance), with both figures and the difference. Read-only.\n\nDrift means a cached balance was not updated with a posting. Reports read cached balances, so check this before a period close.\n\n#### Signature\n\n```http\nGET /business-made/books/balance-drift () -> Drifted accounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/books/balance-drift/repair`"}},"/business-made/books/balance-drift/repair":{"post":{"operationId":"PostingController_repairDrift","summary":"Repair balance drift","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"What was repaired","content":{"application/json":{"schema":{"type":"object","properties":{"repaired":{"type":"number"},"accounts":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The drift found before repair."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"Rewrites the cached debit, credit and net balance of every drifted account from its posted journal entries. No journal entry is written or changed — the posted entries are the source of truth and the cache is brought back to them. Safe to run repeatedly.\n\n#### Signature\n\n```http\nPOST /business-made/books/balance-drift/repair () -> What was repaired\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/balance-drift`"}},"/business-made/books/trial-balance":{"get":{"operationId":"PostingController_trialBalance","summary":"Get the posting trial balance","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"The trial balance","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Books"],"description":"The trial balance from the posting layer. Compare it against `reports/trial-balance` — a difference between them points at drift.\n\n#### Signature\n\n```http\nGET /business-made/books/trial-balance () -> The trial balance\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/books/reports/trial-balance`"}},"/business-made/readiness/settings":{"get":{"operationId":"WorkforceJobsController_getSettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ timezone, escalation, journeyEscalation, jobs:[JobRow], groups:[{group,label}], options }","content":{"application/json":{"example":{"timezone":{"value":"","effective":"America/Los_Angeles","business":"America/Los_Angeles"},"escalation":{"reminderDays":[60,30,14,7],"notifyManagerAtDays":14,"notifyHrAtDays":7,"flagShiftsAtDays":7},"journeyEscalation":{"managerAfterDays":2,"hrAfterDays":4},"jobs":[{"job":"readiness-daily","label":"Readiness daily run","kind":"daily","enabled":true,"available":true,"time":"04:00","cron":"0 4 * * *","timezone":"America/Los_Angeles","schedule":"Daily at 04:00 (America/Los_Angeles)","scheduleId":"66f5…","queued":true,"nextRun":"2026-09-27T11:00:00.000Z","lastRun":{"at":"2026-09-26T11:00:00.212Z","trigger":"schedule","ok":true,"ms":8123,"summary":{"assigned":2,"withdrawn":0,"renewed":1,"expired":0,"notices":4,"flagged":1,"reoffered":0}}},{"job":"journeys-sweep","kind":"interval","enabled":true,"intervalMinutes":10,"cron":"*/10 * * * *","schedule":"Every 10 minutes","queued":true,"nextRun":"2026-09-26T16:40:00.000Z","lastRun":{"at":"2026-09-26T16:30:00.051Z","trigger":"schedule","ok":true,"ms":911,"summary":{"journeys":3,"escalated":0}}},{"job":"journey-exact","kind":"exact","enabled":true,"schedule":"At each task’s own time","pending":1,"nextRun":"2026-09-30T00:59:59.999Z","nextTask":"Revoke access"}],"options":{"intervals":[{"value":5,"label":"Every 5 minutes"},{"value":10,"label":"Every 10 minutes"}]}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Background jobs: settings and status","description":"Every readiness background job is a per-org job on the platform schedule queue (a `schedule` record named `workforce-job:<job>`, queued as `<org>-<scheduleId>-cron`), never a server-wide loop. Jobs: `readiness-daily` (sync, renewals, expiries, reminder ladder, shift flags — daily at `time`), `journeys-sweep` (every `intervalMinutes`), `journey-exact` (a one-off job per offboarding task at its exact end time), `i9-alerts` (daily HR digest of I-9 deadlines, off by default), `course-sources` (daily re-draft of AI courses whose SOP changed, off by default); and for orgs with leave turned on, `leave-nightly` (accrual, January rollover, mark taken — daily 03:00) and `leave-digest` (approver digest — daily 08:00). Only the groups the org uses are listed (`groups`). Daily jobs run in `timezone.effective`: the settings zone, else the business time zone. `escalation` holds the org defaults a rule's own escalation overrides field by field; `journeyEscalation` sets when overdue journey tasks go to the manager and to HR. Jobs are set up when the org gets its first requirement rule or journey, on every server start (re-queued if Redis lost them), and on save.\n\n#### Signature\n\n```http\nGET /business-made/readiness/settings () -> { timezone, escalation, journeyEscalation, jobs:[JobRow], groups:[{group,label}], options }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]},"put":{"operationId":"WorkforceJobsController_saveSettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Same as GET /business-made/readiness/settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Use HH:mm, 24-hour — An unknown job, a bad time, zone or interval, or HR set to be told before the manager.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Use HH:mm, 24-hour","path":"/business-made/readiness/settings","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Save background job settings","description":"Partial body, merged over what is stored (bucket `readiness` of the org base-setting; the leave jobs' switch and time are written to `leave.jobs`, leaving every other leave setting as it was). Saving re-schedules the org's jobs at once: a changed time, zone or interval replaces the queued repeat, a job turned off is stopped (its record keeps the last result), and turning `journey-exact` off or on removes or books the one-off jobs of every open exact-time task. Returns the same shape as GET.\n\n#### Signature\n\n```http\nPUT /business-made/readiness/settings (body) -> Same as GET /business-made/readiness/settings\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SETTINGS_INVALID | Use HH:mm, 24-hour | An unknown job, a bad time, zone or interval, or HR set to be told before the manager. | The body carries `problems: [{ field, message }]`. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"timezone":{"type":"string","description":"IANA zone; empty = business time zone"},"jobs":{"type":"object","additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"time":{"type":"string","pattern":"^HH:mm$"},"intervalMinutes":{"type":"integer","enum":[5,10,15,20,30,60,120,180,240,360,720]},"withinDays":{"type":"integer","minimum":1,"maximum":365}}}},"escalation":{"type":"object","properties":{"reminderDays":{"type":"array","items":{"type":"integer"}},"notifyManagerAtDays":{"type":"integer"},"notifyHrAtDays":{"type":"integer"},"flagShiftsAtDays":{"type":"integer"}}},"journeyEscalation":{"type":"object","properties":{"managerAfterDays":{"type":"integer"},"hrAfterDays":{"type":"integer"}}}}},"example":{"timezone":"America/New_York","jobs":{"readiness-daily":{"time":"05:30"},"journeys-sweep":{"intervalMinutes":15},"i9-alerts":{"enabled":true,"withinDays":60}},"journeyEscalation":{"managerAfterDays":1,"hrAfterDays":3}}}}}}},"/business-made/readiness/jobs":{"get":{"operationId":"WorkforceJobsController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"JobRow[]","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"One background job for this org, as scheduled now. Computed on the server.","properties":{"job":{"type":"string"},"group":{"type":"string","enum":["readiness","leave"]},"label":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string","enum":["daily","interval","exact"]},"enabled":{"type":"boolean"},"available":{"type":"boolean","description":"This server has the job's handler."},"time":{"type":"string"},"intervalMinutes":{"type":"integer"},"withinDays":{"type":"integer"},"cron":{"type":"string"},"timezone":{"type":"string"},"schedule":{"type":"string","description":"Plain words"},"scheduleId":{"type":"string","nullable":true},"queued":{"type":"boolean","description":"The repeat is in the queue."},"nextRun":{"type":"string","nullable":true},"pending":{"type":"integer"},"nextTask":{"type":"string","nullable":true},"lastRun":{"type":"object","nullable":true,"properties":{"at":{"type":"string"},"trigger":{"type":"string","enum":["schedule","manual"]},"ok":{"type":"boolean"},"ms":{"type":"integer"},"summary":{"type":"object","additionalProperties":true},"error":{"type":"string"},"by":{"type":"string"}}},"warning":{"type":"string"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Background jobs","description":"The `jobs` rows of GET settings.\n\n#### Signature\n\n```http\nGET /business-made/readiness/jobs () -> JobRow[]\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/jobs/{job}/run":{"post":{"operationId":"WorkforceJobsController_runNow","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"job","required":true,"in":"path","schema":{"type":"string","enum":["readiness-daily","journeys-sweep","journey-exact","i9-alerts","course-sources","leave-nightly","leave-digest"]}}],"responses":{"201":{"description":"{ result:{ at, trigger:\"manual\", ok, ms, summary?, error?, by }, job: JobRow }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job not found — No job has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Job not found","path":"/business-made/readiness/jobs/{job}/run","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Readiness daily run is already running — A scheduled or manual run of the same job for this org has not finished.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Readiness daily run is already running","path":"/business-made/readiness/jobs/{job}/run","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Run a job now","description":"Runs the job for this org immediately (even when it is turned off) and waits for it. `journey-exact` run by hand runs every exact-time task already due. The result is stored as the job's last run.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/jobs/{job}/run (job: string) -> { result:{ at, trigger:\"manual\", ok, ms, summary?, error?, by }, job: JobRow }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | JOB_RUNNING | Readiness daily run is already running | A scheduled or manual run of the same job for this org has not finished. | Wait and look at the last run. |\n| `404` | JOB_NOT_FOUND | Job not found | No job has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/bank/status":{"get":{"operationId":"BankController_status","parameters":[{"name":"orgid","in":"header","required":true,"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","schema":{"type":"string"},"example":"acme-retail"}],"responses":{"200":{"description":"Feed availability","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean"},"env":{"type":"string","enum":["sandbox","development","production"]}}},"example":{"configured":true,"env":"production"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get bank feed availability","description":"Whether the server has Plaid credentials, and which Plaid environment it talks to. Check it before offering \"Connect a bank\".\n\n#### Signature\n\n```http\nGET /business-made/bank/status () -> Feed availability\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank feeds"]}},"/business-made/bank/mfa":{"get":{"operationId":"BankController_mfaStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Second-factor state","content":{"application/json":{"schema":{"type":"object","properties":{"required":{"type":"boolean"},"enabled":{"type":"boolean"},"method":{"type":"string","enum":["authenticator","email","sms","none"]}}},"example":{"required":true,"enabled":false,"method":"none"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get the caller’s second-factor state","description":"Linking a bank requires the caller to have two-step verification on. `required` is always true; `enabled` says whether this caller can link now.\n\n#### Signature\n\n```http\nGET /business-made/bank/mfa () -> Second-factor state\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bank/link-token`","tags":["Business Made · Bank feeds"]}},"/business-made/bank/link-token":{"post":{"operationId":"BankController_linkToken","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The Link token","content":{"application/json":{"schema":{"type":"object","properties":{"link_token":{"type":"string"},"expiration":{"type":"string"}}},"example":{"link_token":"link-production-8a1b…","expiration":"2026-09-29T18:30:00Z"}}}},"401":{"description":"Sign in to connect a bank — The request has no signed-in user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to connect a bank","path":"/business-made/bank/link-token","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor. — The caller does not have two-step verification (authenticator, email or SMS code) enabled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor.","path":"/business-made/bank/link-token","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Plaid: <provider message> — Plaid rejected the call (bad or expired token, institution down, product not enabled).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"Plaid: <provider message>","path":"/business-made/bank/link-token","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"503":{"description":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. — The server has no Plaid credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server.","path":"/business-made/bank/link-token","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Create a Plaid Link token","description":"Starts linking a bank: returns the short-lived token the Plaid Link widget opens with. Refused unless the caller has two-step verification on.\n\n#### Signature\n\n```http\nPOST /business-made/bank/link-token () -> The Link token\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | AUTH_REQUIRED | Sign in to connect a bank | The request has no signed-in user. | — |\n| `412` | MFA_REQUIRED | Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor. | The caller does not have two-step verification (authenticator, email or SMS code) enabled. | Enable two-step verification on the caller’s profile, then retry. `GET /business-made/bank/mfa` reports the state. |\n| `503` | — | Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. | The server has no Plaid credentials. | — |\n| `502` | — | Plaid: <provider message> | Plaid rejected the call (bad or expired token, institution down, product not enabled). | — |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bank/exchange`\n- `GET /business-made/bank/mfa`","tags":["Business Made · Bank feeds"]}},"/business-made/bank/exchange":{"post":{"operationId":"BankController_exchange","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The connection and its accounts","content":{"application/json":{"schema":{"type":"object","properties":{"connection":{"type":"object","description":"A bank connection (`bank_connection`). The access token is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"Chase connection"},"provider":{"type":"string","enum":["plaid"]},"itemId":{"type":"string","description":"Plaid item id."},"institution":{"type":"object","properties":{"id":{"type":"string","example":"ins_56"},"name":{"type":"string","example":"Chase"}}},"status":{"type":"string","example":"active"},"verified":{"type":"boolean","description":"Account and routing numbers were returned by Plaid Auth."},"lastSyncedAt":{"type":"string","nullable":true},"lastBalanceAt":{"type":"string","nullable":true}}}}},"accounts":{"type":"array","items":{"type":"object","description":"A linked bank account (`bank_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"ledgerAccountId":{"type":"string","description":"The ledger account this bank account feeds."},"glAccountCode":{"type":"string","example":"1010"},"name":{"type":"string","example":"Business Checking"},"source":{"type":"string","enum":["plaid"]},"status":{"type":"string","enum":["active","disconnected"]},"connectionId":{"type":"string"},"providerAccountId":{"type":"string"},"mask":{"type":"string","example":"0000"},"type":{"type":"string","example":"depository","description":"Plaid type: depository, credit, loan, investment…"},"subtype":{"type":"string","example":"checking"},"currency":{"type":"string","example":"USD"},"currentBalance":{"type":"number","example":12840.55},"availableBalance":{"type":"number","nullable":true},"verified":{"type":"boolean"},"routingNumber":{"type":"string","nullable":true},"accountNumberMask":{"type":"string","nullable":true,"example":"••••0000"}}}}}}}}}}},"401":{"description":"Sign in to connect a bank — The request has no signed-in user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to connect a bank","path":"/business-made/bank/exchange","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor. — The caller does not have two-step verification (authenticator, email or SMS code) enabled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor.","path":"/business-made/bank/exchange","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Plaid: <provider message> — Plaid rejected the call (bad or expired token, institution down, product not enabled).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"Plaid: <provider message>","path":"/business-made/bank/exchange","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"503":{"description":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. — The server has no Plaid credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server.","path":"/business-made/bank/exchange","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Link a bank","description":"Finishes linking: exchanges the `publicToken` Plaid Link returned, stores the connection with its access token sealed, and creates one `bank_account` per account at the bank — each given its own ledger account. Where Plaid Auth is available the account and routing numbers are recorded and the account is marked `verified`. The first transaction sync starts right away in the background, so the feed is not empty.\n\n#### Signature\n\n```http\nPOST /business-made/bank/exchange (body) -> The connection and its accounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- The connection record returned here carries the sealed token; list connections with `GET /business-made/bank/connections`, which strips it.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | AUTH_REQUIRED | Sign in to connect a bank | The request has no signed-in user. | — |\n| `412` | MFA_REQUIRED | Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor. | The caller does not have two-step verification (authenticator, email or SMS code) enabled. | Enable two-step verification on the caller’s profile, then retry. `GET /business-made/bank/mfa` reports the state. |\n| `503` | — | Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. | The server has no Plaid credentials. | — |\n| `502` | — | Plaid: <provider message> | Plaid rejected the call (bad or expired token, institution down, product not enabled). | — |\n\nPlus the standard platform errors: `403`, `429`, `500`.\n\n#### See also\n\n- `GET /business-made/bank/connections`\n- `POST /business-made/bank/connections/{id}/sync`","tags":["Business Made · Bank feeds"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["publicToken"],"properties":{"publicToken":{"type":"string"}}},"example":{"publicToken":"public-production-3f2e…"}}}}}},"/business-made/bank/sandbox/connect":{"post":{"operationId":"BankController_sandboxConnect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The connection and its accounts","content":{"application/json":{"schema":{"type":"object","properties":{"connection":{"type":"object","description":"A bank connection (`bank_connection`). The access token is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"Chase connection"},"provider":{"type":"string","enum":["plaid"]},"itemId":{"type":"string","description":"Plaid item id."},"institution":{"type":"object","properties":{"id":{"type":"string","example":"ins_56"},"name":{"type":"string","example":"Chase"}}},"status":{"type":"string","example":"active"},"verified":{"type":"boolean","description":"Account and routing numbers were returned by Plaid Auth."},"lastSyncedAt":{"type":"string","nullable":true},"lastBalanceAt":{"type":"string","nullable":true}}}}},"accounts":{"type":"array","items":{"type":"object","description":"A linked bank account (`bank_account`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"ledgerAccountId":{"type":"string","description":"The ledger account this bank account feeds."},"glAccountCode":{"type":"string","example":"1010"},"name":{"type":"string","example":"Business Checking"},"source":{"type":"string","enum":["plaid"]},"status":{"type":"string","enum":["active","disconnected"]},"connectionId":{"type":"string"},"providerAccountId":{"type":"string"},"mask":{"type":"string","example":"0000"},"type":{"type":"string","example":"depository","description":"Plaid type: depository, credit, loan, investment…"},"subtype":{"type":"string","example":"checking"},"currency":{"type":"string","example":"USD"},"currentBalance":{"type":"number","example":12840.55},"availableBalance":{"type":"number","nullable":true},"verified":{"type":"boolean"},"routingNumber":{"type":"string","nullable":true},"accountNumberMask":{"type":"string","nullable":true,"example":"••••0000"}}}}}}}}}}},"401":{"description":"Sign in to connect a bank — The request has no signed-in user.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Sign in to connect a bank","path":"/business-made/bank/sandbox/connect","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"sandbox helper only available in sandbox — The server is not running Plaid in sandbox.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"sandbox helper only available in sandbox","path":"/business-made/bank/sandbox/connect","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"412":{"description":"Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor. — The caller does not have two-step verification (authenticator, email or SMS code) enabled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor.","path":"/business-made/bank/sandbox/connect","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Plaid: <provider message> — Plaid rejected the call (bad or expired token, institution down, product not enabled).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"Plaid: <provider message>","path":"/business-made/bank/sandbox/connect","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"503":{"description":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. — The server has no Plaid credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server.","path":"/business-made/bank/sandbox/connect","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Link a sandbox bank","description":"Testing convenience: links a fake Plaid sandbox institution without the Link UI, exactly as `exchange` would. Only works when the server runs Plaid in `sandbox`.\n\n#### Signature\n\n```http\nPOST /business-made/bank/sandbox/connect (body) -> The connection and its accounts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | AUTH_REQUIRED | Sign in to connect a bank | The request has no signed-in user. | — |\n| `412` | MFA_REQUIRED | Turn on two-step verification in your profile before connecting a bank. Bank connections are protected by a second factor. | The caller does not have two-step verification (authenticator, email or SMS code) enabled. | Enable two-step verification on the caller’s profile, then retry. `GET /business-made/bank/mfa` reports the state. |\n| `403` | — | sandbox helper only available in sandbox | The server is not running Plaid in sandbox. | — |\n| `503` | — | Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. | The server has no Plaid credentials. | — |\n| `502` | — | Plaid: <provider message> | Plaid rejected the call (bad or expired token, institution down, product not enabled). | — |\n\nPlus the standard platform errors: `429`, `500`.","tags":["Business Made · Bank feeds"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string","example":"ins_109508","description":"Sandbox institution. Default: ins_109508 (First Platypus Bank)."}}}}}}}},"/business-made/bank/connections":{"get":{"operationId":"BankController_connections","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Connections","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A bank connection (`bank_connection`). The access token is never included.","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"Chase connection"},"provider":{"type":"string","enum":["plaid"]},"itemId":{"type":"string","description":"Plaid item id."},"institution":{"type":"object","properties":{"id":{"type":"string","example":"ins_56"},"name":{"type":"string","example":"Chase"}}},"status":{"type":"string","example":"active"},"verified":{"type":"boolean","description":"Account and routing numbers were returned by Plaid Auth."},"lastSyncedAt":{"type":"string","nullable":true},"lastBalanceAt":{"type":"string","nullable":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List bank connections","description":"Every linked bank (up to 100), without access tokens.\n\n#### Signature\n\n```http\nGET /business-made/bank/connections () -> Connections\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bank/exchange`","tags":["Business Made · Bank feeds"]}},"/business-made/bank/balances":{"get":{"operationId":"BankController_balances","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Balance summary","content":{"application/json":{"schema":{"type":"object","properties":{"accountCount":{"type":"number"},"currencies":{"type":"array","items":{"type":"object","properties":{"currency":{"type":"string"},"cash":{"type":"number"},"debt":{"type":"number"},"net":{"type":"number"},"accounts":{"type":"number"}}}},"asOf":{"type":"string","nullable":true}}},"example":{"accountCount":3,"currencies":[{"currency":"USD","cash":48210.12,"debt":3120.5,"net":45089.62,"accounts":3}],"asOf":"2026-09-29T14:02:11Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get balances across banks","description":"The stored balances of every active linked account, grouped by currency. Depository accounts count as `cash`, credit and loan accounts as `debt`; `net` = cash − debt + other. `asOf` is the latest balance refresh. Reads what is stored — refresh first for live figures.\n\n#### Signature\n\n```http\nGET /business-made/bank/balances () -> Balance summary\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bank/refresh-balances`","tags":["Business Made · Bank feeds"]}},"/business-made/bank/refresh-balances":{"post":{"operationId":"BankController_refreshBalances","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"refreshed":{"type":"number","description":"Accounts updated."},"errors":{"type":"array","items":{"type":"string"}}}},"example":{"refreshed":3,"errors":[]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. — The server has no Plaid credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server.","path":"/business-made/bank/refresh-balances","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Refresh live balances","description":"Asks the bank for real-time balances (Plaid `/accounts/balance/get`, not the cached figures) and writes them onto each account. Without `connectionId` it refreshes every active connection. A bank that fails is reported in `errors`; the others still refresh.\n\n#### Signature\n\n```http\nPOST /business-made/bank/refresh-balances (body) -> Result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | — | Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. | The server has no Plaid credentials. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank feeds"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"connectionId":{"type":"string","description":"Refresh one connection only."}}}}}}}},"/business-made/bank/accounts/map":{"post":{"operationId":"BankController_mapAccounts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Counts","content":{"application/json":{"schema":{"type":"object","properties":{"mapped":{"type":"number"},"alreadyMapped":{"type":"number"}}},"example":{"mapped":1,"alreadyMapped":2}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Give every bank account a ledger account","description":"Creates a ledger account for each linked bank account that has none. Idempotent — accounts already mapped are counted, not changed.\n\n#### Signature\n\n```http\nPOST /business-made/bank/accounts/map () -> Counts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank feeds"]}},"/business-made/bank/connections/reseal":{"post":{"operationId":"BankController_reseal","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Counts","content":{"application/json":{"schema":{"type":"object","properties":{"sealed":{"type":"number"},"already":{"type":"number"}}},"example":{"sealed":1,"already":3}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"Bank connections need FINANCE_ENCRYPTION_KEY on the server before a token can be stored — The server cannot seal the access token at rest.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Bank connections need FINANCE_ENCRYPTION_KEY on the server before a token can be stored","path":"/business-made/bank/connections/reseal","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Seal stored access tokens","description":"Encrypts any access token stored before encryption at rest existed. Idempotent: tokens already sealed are counted, not changed.\n\n#### Signature\n\n```http\nPOST /business-made/bank/connections/reseal () -> Counts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `412` | ENCRYPTION_KEY_MISSING | Bank connections need FINANCE_ENCRYPTION_KEY on the server before a token can be stored | The server cannot seal the access token at rest. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank feeds"]}},"/business-made/bank/connections/{id}":{"delete":{"operationId":"BankController_disconnect","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1c0ffee00a1b2c3d4e5f6"}],"responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"type":"object","properties":{"disconnected":{"type":"boolean"}}},"example":{"disconnected":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Disconnect a bank","description":"Revokes the connection at Plaid (best effort), marks its bank accounts `disconnected` and deletes the connection. The accounts keep their ledger link and history — only the feed stops. Transactions already imported stay.\n\n#### Signature\n\n```http\nDELETE /business-made/bank/connections/{id} (id: string) -> Done\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Does not check that the connection exists: an unknown id still answers `{ disconnected: true }`.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank feeds"]}},"/business-made/bank/connections/{id}/sync":{"post":{"operationId":"BankController_sync","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1c0ffee00a1b2c3d4e5f6"}],"responses":{"201":{"description":"What changed","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"number"},"modified":{"type":"number"},"removed":{"type":"number"}}},"example":{"added":42,"modified":3,"removed":0}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Bank connection not found — No connection has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Bank connection not found","path":"/business-made/bank/connections/{id}/sync","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Plaid: <provider message> — Plaid rejected the call (bad or expired token, institution down, product not enabled).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"Plaid: <provider message>","path":"/business-made/bank/connections/{id}/sync","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"503":{"description":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. — The server has no Plaid credentials.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server.","path":"/business-made/bank/connections/{id}/sync","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"summary":"Sync a connection’s transactions","description":"Pulls new, changed and removed transactions since the last sync (Plaid cursor sync) and upserts them as `bank_transaction` rows. New lines start `unmatched`. Safe to repeat.\n\n#### Signature\n\n```http\nPOST /business-made/bank/connections/{id}/sync (id: string) -> What changed\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Bank connection not found | No connection has that id. | — |\n| `503` | — | Plaid is not configured — set PLAID_CLIENT_ID and PLAID_SECRET on the server. | The server has no Plaid credentials. | — |\n| `502` | — | Plaid: <provider message> | Plaid rejected the call (bad or expired token, institution down, product not enabled). | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bank/reconcile/auto-match`","tags":["Business Made · Bank feeds"]}},"/business-made/bank/reconcile/summary":{"get":{"operationId":"BankController_summary","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bankAccountId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one linked bank account."}],"responses":{"200":{"description":"Counts","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"number"},"unmatched":{"type":"number"},"matched":{"type":"number"},"confirmed":{"type":"number"},"ignored":{"type":"number"},"pending":{"type":"number"}}},"example":{"total":180,"unmatched":22,"matched":9,"confirmed":141,"ignored":8,"pending":4}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get the reconciliation summary","description":"How many feed lines are unmatched, matched (by auto-match, awaiting confirmation), confirmed and ignored, plus how many are still pending at the bank.\n\n#### Signature\n\n```http\nGET /business-made/bank/reconcile/summary (bankAccountId?: string) -> Counts\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank reconciliation"]}},"/business-made/bank/transactions":{"get":{"operationId":"BankController_transactions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"bankAccountId","required":false,"in":"query","schema":{"type":"string"},"description":"Restrict to one linked bank account."},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["unmatched","matched","confirmed","ignored"]},"description":"Match status."},{"name":"limit","required":false,"in":"query","schema":{"type":"integer"},"description":"Default 500.","example":100}],"responses":{"200":{"description":"Transactions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"A bank feed line (`bank_transaction`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"STRIPE PAYOUT"},"merchantName":{"type":"string","nullable":true},"connectionId":{"type":"string"},"bankAccountId":{"type":"string","nullable":true},"providerTransactionId":{"type":"string"},"amount":{"type":"number","description":"Plaid sign: positive = money out, negative = money in.","example":-1520.4},"direction":{"type":"string","enum":["inflow","outflow"]},"date":{"type":"string","example":"2026-09-18"},"pending":{"type":"boolean"},"category":{"type":"array","items":{"type":"string"}},"isoCurrencyCode":{"type":"string","example":"USD"},"matchStatus":{"type":"string","enum":["unmatched","matched","confirmed","ignored"]},"matchedEntryId":{"type":"string","nullable":true,"description":"The journal entry this line is matched to."},"matchConfidence":{"type":"number","nullable":true,"description":"0–1 score from auto-match; 1 for an entry created from the line."},"reconciledAt":{"type":"string","nullable":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List bank transactions","description":"Feed lines, newest first by date.\n\n#### Signature\n\n```http\nGET /business-made/bank/transactions (bankAccountId?: string, status?: string, limit?: integer) -> Transactions\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank reconciliation"]}},"/business-made/bank/reconcile/auto-match":{"post":{"operationId":"BankController_autoMatch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"scanned":{"type":"number"},"matched":{"type":"number"},"needsReview":{"type":"number"}}},"example":{"scanned":22,"matched":14,"needsReview":8}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Auto-match feed lines to the books","description":"Scores every `unmatched` line against journal entries with the same amount dated within 5 days — closer dates and a description naming the merchant score higher. A line is linked (`matched`) only when the best score is at least 0.75 and clearly ahead of the next; an entry already linked to another line is never reused. Everything else is left for review. Matched lines still need confirming.\n\n#### Signature\n\n```http\nPOST /business-made/bank/reconcile/auto-match (body) -> Result\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- Candidates come from the 2,000 most recent journal entries.\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.\n\n#### See also\n\n- `POST /business-made/bank/transactions/{id}/confirm`","tags":["Business Made · Bank reconciliation"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bankAccountId":{"type":"string","description":"Only this account’s lines."}}}}}}}},"/business-made/bank/transactions/{id}/confirm":{"post":{"operationId":"BankController_confirm","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1c0ffee00a1b2c3d4e5f6"}],"responses":{"201":{"description":"The updated line","content":{"application/json":{"schema":{"type":"object","description":"A bank feed line (`bank_transaction`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"STRIPE PAYOUT"},"merchantName":{"type":"string","nullable":true},"connectionId":{"type":"string"},"bankAccountId":{"type":"string","nullable":true},"providerTransactionId":{"type":"string"},"amount":{"type":"number","description":"Plaid sign: positive = money out, negative = money in.","example":-1520.4},"direction":{"type":"string","enum":["inflow","outflow"]},"date":{"type":"string","example":"2026-09-18"},"pending":{"type":"boolean"},"category":{"type":"array","items":{"type":"string"}},"isoCurrencyCode":{"type":"string","example":"USD"},"matchStatus":{"type":"string","enum":["unmatched","matched","confirmed","ignored"]},"matchedEntryId":{"type":"string","nullable":true,"description":"The journal entry this line is matched to."},"matchConfidence":{"type":"number","nullable":true,"description":"0–1 score from auto-match; 1 for an entry created from the line."},"reconciledAt":{"type":"string","nullable":true}}}}}}}},"400":{"description":"No journal entry to confirm against — The line has no match and no `entryId` was given.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"No journal entry to confirm against","path":"/business-made/bank/transactions/{id}/confirm","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction not found — No bank transaction has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction not found","path":"/business-made/bank/transactions/{id}/confirm","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Confirm a match","description":"Marks the line `confirmed` (reconciled). Pass `entryId` to confirm against a different journal entry than the one auto-match chose.\n\n#### Signature\n\n```http\nPOST /business-made/bank/transactions/{id}/confirm (id: string, body) -> The updated line\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Transaction not found | No bank transaction has that id. | — |\n| `400` | — | No journal entry to confirm against | The line has no match and no `entryId` was given. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank reconciliation"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"entryId":{"type":"string","description":"Journal entry to match. Default: the one already matched."}}}}}}}},"/business-made/bank/transactions/{id}/unmatch":{"post":{"operationId":"BankController_unmatch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1c0ffee00a1b2c3d4e5f6"}],"responses":{"201":{"description":"The updated line","content":{"application/json":{"schema":{"type":"object","description":"A bank feed line (`bank_transaction`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"STRIPE PAYOUT"},"merchantName":{"type":"string","nullable":true},"connectionId":{"type":"string"},"bankAccountId":{"type":"string","nullable":true},"providerTransactionId":{"type":"string"},"amount":{"type":"number","description":"Plaid sign: positive = money out, negative = money in.","example":-1520.4},"direction":{"type":"string","enum":["inflow","outflow"]},"date":{"type":"string","example":"2026-09-18"},"pending":{"type":"boolean"},"category":{"type":"array","items":{"type":"string"}},"isoCurrencyCode":{"type":"string","example":"USD"},"matchStatus":{"type":"string","enum":["unmatched","matched","confirmed","ignored"]},"matchedEntryId":{"type":"string","nullable":true,"description":"The journal entry this line is matched to."},"matchConfidence":{"type":"number","nullable":true,"description":"0–1 score from auto-match; 1 for an entry created from the line."},"reconciledAt":{"type":"string","nullable":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction not found — No bank transaction has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction not found","path":"/business-made/bank/transactions/{id}/unmatch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Undo a match","description":"Sets the line back to `unmatched` and clears its matched entry and confidence.\n\n#### Signature\n\n```http\nPOST /business-made/bank/transactions/{id}/unmatch (id: string) -> The updated line\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Transaction not found | No bank transaction has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank reconciliation"]}},"/business-made/bank/transactions/{id}/ignore":{"post":{"operationId":"BankController_ignore","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1c0ffee00a1b2c3d4e5f6"}],"responses":{"201":{"description":"The updated line","content":{"application/json":{"schema":{"type":"object","description":"A bank feed line (`bank_transaction`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"STRIPE PAYOUT"},"merchantName":{"type":"string","nullable":true},"connectionId":{"type":"string"},"bankAccountId":{"type":"string","nullable":true},"providerTransactionId":{"type":"string"},"amount":{"type":"number","description":"Plaid sign: positive = money out, negative = money in.","example":-1520.4},"direction":{"type":"string","enum":["inflow","outflow"]},"date":{"type":"string","example":"2026-09-18"},"pending":{"type":"boolean"},"category":{"type":"array","items":{"type":"string"}},"isoCurrencyCode":{"type":"string","example":"USD"},"matchStatus":{"type":"string","enum":["unmatched","matched","confirmed","ignored"]},"matchedEntryId":{"type":"string","nullable":true,"description":"The journal entry this line is matched to."},"matchConfidence":{"type":"number","nullable":true,"description":"0–1 score from auto-match; 1 for an entry created from the line."},"reconciledAt":{"type":"string","nullable":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction not found — No bank transaction has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction not found","path":"/business-made/bank/transactions/{id}/ignore","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Ignore a feed line","description":"Marks the line `ignored` — it will not be matched or counted as unreconciled.\n\n#### Signature\n\n```http\nPOST /business-made/bank/transactions/{id}/ignore (id: string) -> The updated line\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Transaction not found | No bank transaction has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank reconciliation"]}},"/business-made/bank/transactions/{id}/create-entry":{"post":{"operationId":"BankController_createEntry","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Record id.","example":"66f1c0ffee00a1b2c3d4e5f6"}],"responses":{"201":{"description":"The posted entry and the confirmed line","content":{"application/json":{"schema":{"type":"object","properties":{"entry":{"type":"object","additionalProperties":true},"transaction":{"type":"object","description":"A bank feed line (`bank_transaction`).","properties":{"id":{"type":"string","description":"Record identifier.","example":"66f1a2b3c4d5e6f708192a3b"},"sk":{"type":"string","description":"Storage sort key."},"datatype":{"type":"string","description":"Record type discriminator.","example":"category"},"name":{"type":"string","description":"Machine name, unique within the datatype."},"title":{"type":"string","description":"Display title."},"createdate":{"type":"string","format":"date-time","description":"Creation timestamp."},"modifydate":{"type":"string","format":"date-time","description":"Last modification timestamp."},"author":{"type":"string","description":"Email or username of the last writer."},"data":{"type":"object","properties":{"name":{"type":"string","example":"STRIPE PAYOUT"},"merchantName":{"type":"string","nullable":true},"connectionId":{"type":"string"},"bankAccountId":{"type":"string","nullable":true},"providerTransactionId":{"type":"string"},"amount":{"type":"number","description":"Plaid sign: positive = money out, negative = money in.","example":-1520.4},"direction":{"type":"string","enum":["inflow","outflow"]},"date":{"type":"string","example":"2026-09-18"},"pending":{"type":"boolean"},"category":{"type":"array","items":{"type":"string"}},"isoCurrencyCode":{"type":"string","example":"USD"},"matchStatus":{"type":"string","enum":["unmatched","matched","confirmed","ignored"]},"matchedEntryId":{"type":"string","nullable":true,"description":"The journal entry this line is matched to."},"matchConfidence":{"type":"number","nullable":true,"description":"0–1 score from auto-match; 1 for an entry created from the line."},"reconciledAt":{"type":"string","nullable":true}}}}}}}}}},"400":{"description":"Could not resolve the cash or counter account. Check your chart of accounts. — `counterAccountCode` is not in the chart of accounts.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Could not resolve the cash or counter account. Check your chart of accounts.","path":"/business-made/bank/transactions/{id}/create-entry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction not found — No bank transaction has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Transaction not found","path":"/business-made/bank/transactions/{id}/create-entry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"412":{"description":"This bank account is not linked to a ledger account yet. Open Books › Bank and choose the GL account it feeds, then add this line to the books. — The line’s bank account has no ledger account or GL code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"This bank account is not linked to a ledger account yet. Open Books › Bank and choose the GL account it feeds, then add this line to the books.","path":"/business-made/bank/transactions/{id}/create-entry","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Book a feed line","description":"For a line with nothing in the books (a bank fee, interest, an owner deposit): creates and posts a journal entry from it, then confirms the line against it. Money out debits `counterAccountCode` and credits the bank account’s ledger account; money in is the reverse. The bank side must be the ledger account this bank account feeds — if the account is not linked to one, the call is refused rather than posting to a guess.\n\n#### Signature\n\n```http\nPOST /business-made/bank/transactions/{id}/create-entry (id: string, body) -> The posted entry and the confirmed line\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | — | Transaction not found | No bank transaction has that id. | — |\n| `412` | — | This bank account is not linked to a ledger account yet. Open Books › Bank and choose the GL account it feeds, then add this line to the books. | The line’s bank account has no ledger account or GL code. | Map it with `POST /business-made/bank/accounts/map` or set its GL account. |\n| `400` | — | Could not resolve the cash or counter account. Check your chart of accounts. | `counterAccountCode` is not in the chart of accounts. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Bank reconciliation"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["counterAccountCode"],"properties":{"counterAccountCode":{"type":"string","description":"GL code of the other side."},"description":{"type":"string","description":"Default: the merchant or line name."},"businessLocationId":{"type":"string"}}},"example":{"counterAccountCode":"6150","description":"Monthly service fee"}}}}}},"/business-made/readiness/overview":{"get":{"operationId":"ReadinessController_overview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"location","required":false,"in":"query","schema":{"type":"string"},"description":"Location name or id"}],"responses":{"200":{"description":"Overview","content":{"application/json":{"example":{"totals":{"employees":212,"requirements":9,"readyRate":87,"overdue":14,"dueSoon":31,"expiring30":6,"blocked":3},"byLocation":[{"location":"Downtown","readyRate":81,"overdue":9,"blocked":2,"employees":64}],"byRequirement":[{"id":"RULE-1","title":"Food handler card (California)","kind":"certification","rate":78,"overdue":7,"dueSoon":4}]}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Readiness overview","description":"Totals, per location and per requirement. Rates are 0–100. `readyRate` = share of people with requirements who have nothing overdue or expired. `dueSoon` = open items due in 30 days; `expiring30` = completions expiring in 30 days; `blocked` = people blocked at one or more gates.\n\n#### Signature\n\n```http\nGET /business-made/readiness/overview (location: string) -> Overview\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/matrix":{"get":{"operationId":"ReadinessController_matrix","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"location","in":"query","required":false,"schema":{"type":"string"}},{"name":"department","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"requirement","in":"query","required":false,"schema":{"type":"string"}},{"name":"employee","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"string"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Matrix page","content":{"application/json":{"example":{"requirements":[{"id":"RULE-1","title":"Food handler card","kind":"certification"}],"rows":[{"employeeId":"EMP-1043","name":"Luis Ramirez","location":"Downtown","department":"Kitchen","position":"Line cook","overall":50,"cells":{"RULE-1":{"status":"expired","dueDate":"2026-03-02","completedAt":"2023-03-02T00:00:00.000Z","expiresAt":"2026-03-02T23:59:59.999Z","evidenceRef":{"datatype":"bm_employee_document","id":"DOC-9"}}}}],"total":1,"page":0,"pageSize":50}}}},"400":{"description":"status must be one of …, ready, not_ready, blocked — `status` is not a requirement status or ready / not_ready / blocked.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"status must be one of …, ready, not_ready, blocked","path":"/business-made/readiness/matrix","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"People × requirements matrix","description":"Server-side filtered and paged. Every row carries all of its cells (only requirements that apply to that person). `status` filters on a cell status, or `ready`, `not_ready`, `blocked`. Page is 0-based.\n\n#### Signature\n\n```http\nGET /business-made/readiness/matrix (location: string, department: string, status: string, requirement: string, employee: string, search: string, page: string, pageSize: string) -> Matrix page\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BAD_FILTER | status must be one of …, ready, not_ready, blocked | `status` is not a requirement status or ready / not_ready / blocked. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/employee/{employeeId}":{"get":{"operationId":"ReadinessController_employee","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Badge number (employeeId) or record id"}],"responses":{"200":{"description":"{ employee, overall, byGate, items, history }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/readiness/employee/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"One person: requirements, gates, overrides and history","description":"Score, effect per gate (ok/warn/block), every applicable requirement with evidence and overrides, and the full ledger history (newest first; each entry has its implied `to`).\n\n#### Signature\n\n```http\nGET /business-made/readiness/employee/{employeeId} (employeeId: string) -> { employee, overall, byGate, items, history }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/check/{employeeId}":{"get":{"operationId":"ReadinessController_check","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"}},{"name":"gate","in":"query","required":true,"schema":{"type":"string","enum":["scheduler","clockIn","pos","kitchenStation","payroll","access"]}},{"name":"at","in":"query","required":false,"schema":{"type":"string"}},{"name":"station","in":"query","required":false,"schema":{"type":"string"}},{"name":"locationId","in":"query","required":false,"schema":{"type":"string"}},{"name":"permissionScope","in":"query","required":false,"schema":{"type":"string"}},{"name":"shiftId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ effect, blocking, warnings, overrideActive? }","content":{"application/json":{"example":{"effect":"block","blocking":[{"requirementId":"RULE-1","title":"Food handler card","kind":"certification","status":"expired","expiresAt":"2026-03-02T23:59:59.999Z","graceUntil":"2026-03-09T23:59:59.999Z","effect":"block"}],"warnings":[]}}}},"400":{"description":"gate is required — `gate` is missing or unknown (\"Unknown gate \"x\"\"; the body lists the `gates`).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"gate is required","path":"/business-made/readiness/check/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/readiness/check/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Gate check","description":"The same answer a gate gets from `ReadinessService.check`. `effect`: none; warn (let through, show warnings: inside grace, a warn rule, or a block covered by an active override, then `overrideActive` is set); override (stop unless an override is recorded); block (stop). `at` evaluates at another instant (a future shift start). A POS rule with a permissionScope applies only when the same `permissionScope` is passed. A station-scoped rule with no station known only warns.\n\n#### Signature\n\n```http\nGET /business-made/readiness/check/{employeeId} (employeeId: string, gate: string, at: string, station: string, locationId: string, permissionScope: string, shiftId: string) -> { effect, blocking, warnings, overrideActive? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BAD_GATE | gate is required | `gate` is missing or unknown (\"Unknown gate \"x\"\"; the body lists the `gates`). | — |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/rules":{"get":{"operationId":"ReadinessController_listRules","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["course","certification","policy","document","form","signoff","task"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","active","retired"]}},{"name":"packId","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"string"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ data, total, page, pageSize }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List requirement rules","description":"All rules, sorted by title, filtered and paged on the server (page is 0-based).\n\n#### Signature\n\n```http\nGET /business-made/readiness/rules (kind: string, status: string, packId: string, search: string, page: string, pageSize: string) -> { data, total, page, pageSize }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]},"post":{"operationId":"ReadinessController_createRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The created rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The requirement rule is not valid — A field is missing or has a bad value (title and kind are required; an active rule must name what proves it).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The requirement rule is not valid","path":"/business-made/readiness/rules","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a requirement rule","description":"Body is the bm_requirement_rule data (or `{ data }`). New rules are drafts, and every gate not configured defaults to `warn` (access stays off until roles are named). Also enforced on the generic repository path.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/rules (body) -> The created rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RULE_INVALID | The requirement rule is not valid | A field is missing or has a bad value (title and kind are required; an active rule must name what proves it). | The body carries `problems: [{ field, message }]`. Fix each field and save again. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"Food handler card":{"value":{"title":"Food handler card","kind":"certification","target":{"certificationId":"CERT-1"},"appliesTo":{"departments":["Kitchen"]},"due":{"dueFrom":"start_date","dueWithinDays":30,"renewalMonths":36},"enforcement":{"kitchenStation":{"effect":"block","graceDays":7}}}},"Within 30 days or 100 hours worked":{"value":{"title":"Harassment prevention (temporary)","kind":"course","appliesTo":{"employmentTypes":["temporary"]},"due":{"dueFrom":"start_date","dueWithinDays":30,"dueWithinHoursWorked":100,"renewalMonths":24}}},"Every calendar year by 31 December":{"value":{"title":"Harassment prevention (Illinois)","kind":"course","due":{"dueFrom":"start_date","cadence":"training_year","trainingYearStart":"01-01"}}},"Once 80 hours and 90 days are met":{"value":{"title":"Harassment prevention (NYC)","kind":"course","appliesTo":{"minDaysEmployed":90,"hoursWorkedInYearOver":80},"due":{"dueFrom":"threshold_met","dueWithinDays":30,"cadence":"training_year","trainingYearStart":"01-01"}}}}}}}}},"/business-made/readiness/rules/{id}":{"get":{"operationId":"ReadinessController_getRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"sk or code"}],"responses":{"200":{"description":"The rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Requirement not found — No requirement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Requirement not found","path":"/business-made/readiness/rules/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a requirement rule","description":"#### Signature\n\n```http\nGET /business-made/readiness/rules/{id} (id: string) -> The rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUIREMENT_NOT_FOUND | Requirement not found | No requirement has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]},"put":{"operationId":"ReadinessController_updateRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The updated rule","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The requirement rule is not valid — A field is missing or has a bad value (title and kind are required; an active rule must name what proves it).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The requirement rule is not valid","path":"/business-made/readiness/rules/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Requirement not found — No requirement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Requirement not found","path":"/business-made/readiness/rules/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update a requirement rule","description":"Merges the body over the stored data and validates the result. The rule is re-synced for everyone a few seconds later.\n\n#### Signature\n\n```http\nPUT /business-made/readiness/rules/{id} (id: string, body) -> The updated rule\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RULE_INVALID | The requirement rule is not valid | A field is missing or has a bad value (title and kind are required; an active rule must name what proves it). | The body carries `problems: [{ field, message }]`. Fix each field and save again. |\n| `404` | REQUIREMENT_NOT_FOUND | Requirement not found | No requirement has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}},"delete":{"operationId":"ReadinessController_deleteRule","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"{ id, deleted, retired, message? }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Requirement not found — No requirement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Requirement not found","path":"/business-made/readiness/rules/{id}","method":"DELETE","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete (or retire) a requirement rule","description":"A rule with ledger history is retired instead of deleted, and its open items are withdrawn.\n\n#### Signature\n\n```http\nDELETE /business-made/readiness/rules/{id} (id: string) -> { id, deleted, retired, message? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUIREMENT_NOT_FOUND | Requirement not found | No requirement has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/rules/{id}/preview":{"post":{"operationId":"ReadinessController_preview","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"{ matches:[{employeeId,name,location,position}], count }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Requirement not found — No requirement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Requirement not found","path":"/business-made/readiness/rules/{id}/preview","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Who a rule applies to","description":"Scope only, ignoring the rule status. Unsaved edits in the body are applied first. `id` may be `new`.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/rules/{id}/preview (id: string, body) -> { matches:[{employeeId,name,location,position}], count }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | REQUIREMENT_NOT_FOUND | Requirement not found | No requirement has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/business-made/readiness/sync":{"post":{"operationId":"ReadinessController_sync","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ assigned, withdrawn, renewed, expired }","content":{"application/json":{"example":{"assigned":12,"withdrawn":1,"renewed":3,"expired":2}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Re-evaluate and write the ledger","description":"Assigns and withdraws, opens renewals and marks expiries now (the `readiness-daily` job does the same at the time in the readiness settings, 04:00 business time by default). `{ requirementId }` limits to one rule; `{ employeeId }` to one person; `{ escalate: true }` also runs the reminder ladder.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/sync (body) -> { assigned, withdrawn, renewed, expired }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"requirementId":"RULE-1"}}}}}},"/business-made/readiness/job":{"get":{"operationId":"ReadinessController_jobStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"JobRow","content":{"application/json":{"schema":{"type":"object","description":"One background job for this org, as scheduled now. Computed on the server.","properties":{"job":{"type":"string"},"group":{"type":"string","enum":["readiness","leave"]},"label":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string","enum":["daily","interval","exact"]},"enabled":{"type":"boolean"},"available":{"type":"boolean","description":"This server has the job's handler."},"time":{"type":"string"},"intervalMinutes":{"type":"integer"},"withinDays":{"type":"integer"},"cron":{"type":"string"},"timezone":{"type":"string"},"schedule":{"type":"string","description":"Plain words"},"scheduleId":{"type":"string","nullable":true},"queued":{"type":"boolean","description":"The repeat is in the queue."},"nextRun":{"type":"string","nullable":true},"pending":{"type":"integer"},"nextTask":{"type":"string","nullable":true},"lastRun":{"type":"object","nullable":true,"properties":{"at":{"type":"string"},"trigger":{"type":"string","enum":["schedule","manual"]},"ok":{"type":"boolean"},"ms":{"type":"integer"},"summary":{"type":"object","additionalProperties":true},"error":{"type":"string"},"by":{"type":"string"}}},"warning":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Daily run status","description":"The `readiness-daily` row of GET /business-made/readiness/settings.\n\n#### Signature\n\n```http\nGET /business-made/readiness/job () -> JobRow\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/overrides":{"post":{"operationId":"ReadinessController_override","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ id }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The override is incomplete — gate, reasonCode, reason or a future expiresAt is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The override is incomplete","path":"/business-made/readiness/overrides","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the location manager or HR can approve an override — The caller is not HR (Owner/ConfigAdmin/RootAdmin, an HR role, or named HR in leave settings) nor a manager at the person's location. The person's own supervisor alone is refused, as is the person themselves.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the location manager or HR can approve an override","path":"/business-made/readiness/overrides","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Requirement not found — No requirement has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Requirement not found","path":"/business-made/readiness/overrides","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record an override","description":"Lets someone past a gate for a requirement until `expiresAt` (optionally for one shift). The approver is the caller and must be the location manager or HR, never the person or their supervisor alone. Written to the ledger as an `override` entry.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/overrides (body) -> { id }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | APPROVER_NOT_ALLOWED | Only the location manager or HR can approve an override | The caller is not HR (Owner/ConfigAdmin/RootAdmin, an HR role, or named HR in leave settings) nor a manager at the person's location. The person's own supervisor alone is refused, as is the person themselves. | — |\n| `400` | OVERRIDE_INVALID | The override is incomplete | gate, reasonCode, reason or a future expiresAt is missing. | The body carries `problems: [{ field, message }]`. |\n| `404` | REQUIREMENT_NOT_FOUND | Requirement not found | No requirement has that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-1043","requirementId":"RULE-1","gate":"clockIn","reasonCode":"renewal_booked","reason":"Exam booked for Friday","expiresAt":"2026-10-03T23:59:00Z"}}}}},"get":{"operationId":"ReadinessController_overrides","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"active","in":"query","required":false,"schema":{"type":"string"}},{"name":"employee","in":"query","required":false,"schema":{"type":"string"}},{"name":"location","in":"query","required":false,"schema":{"type":"string"}},{"name":"requirement","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"string"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ data, total, page, pageSize }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List overrides and waivers","description":"#### Signature\n\n```http\nGET /business-made/readiness/overrides (active: string, employee: string, location: string, requirement: string, page: string, pageSize: string) -> { data, total, page, pageSize }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/waivers":{"post":{"operationId":"ReadinessController_waive","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ id }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The waiver is incomplete — Required waiver fields are missing; the body lists `problems`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The waiver is incomplete","path":"/business-made/readiness/waivers","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the location manager or HR can approve an override — The caller is not HR (Owner/ConfigAdmin/RootAdmin, an HR role, or named HR in leave settings) nor a manager at the person's location. The person's own supervisor alone is refused, as is the person themselves.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the location manager or HR can approve an override","path":"/business-made/readiness/waivers","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Waive a requirement for a person","description":"Same approver rule as overrides. Optional `expiresAt`; empty = until revoked.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/waivers (body) -> { id }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | APPROVER_NOT_ALLOWED | Only the location manager or HR can approve an override | The caller is not HR (Owner/ConfigAdmin/RootAdmin, an HR role, or named HR in leave settings) nor a manager at the person's location. The person's own supervisor alone is refused, as is the person themselves. | — |\n| `400` | WAIVER_INVALID | The waiver is incomplete | Required waiver fields are missing; the body lists `problems`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-1043","requirementId":"RULE-1","reasonCode":"not_applicable","reason":"Front of house only"}}}}}},"/business-made/readiness/waivers/{id}/revoke":{"post":{"operationId":"ReadinessController_revokeWaiver","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"{ id, status }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only the location manager or HR can approve an override — The caller is not HR (Owner/ConfigAdmin/RootAdmin, an HR role, or named HR in leave settings) nor a manager at the person's location. The person's own supervisor alone is refused, as is the person themselves.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only the location manager or HR can approve an override","path":"/business-made/readiness/waivers/{id}/revoke","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Waiver not found — No waiver ledger entry has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Waiver not found","path":"/business-made/readiness/waivers/{id}/revoke","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"End a waiver","description":"Writes a new status entry; the waiver entry is never changed.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/waivers/{id}/revoke (id: string) -> { id, status }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | APPROVER_NOT_ALLOWED | Only the location manager or HR can approve an override | The caller is not HR (Owner/ConfigAdmin/RootAdmin, an HR role, or named HR in leave settings) nor a manager at the person's location. The person's own supervisor alone is refused, as is the person themselves. | — |\n| `404` | WAIVER_NOT_FOUND | Waiver not found | No waiver ledger entry has that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/evidence":{"post":{"operationId":"ReadinessController_evidence","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ ok: true }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"employeeId and evidence { datatype, id } are required — A required field is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"employeeId and evidence { datatype, id } are required","path":"/business-made/readiness/evidence","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/readiness/evidence","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Record proof","description":"Same as `ReadinessService.recordEvidence`. For forms, tasks and manual proof with a `requirementId`, the proof is written to the ledger; for courses, documents, acknowledgements and sign-offs the record itself is read.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/evidence (body) -> { ok: true }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EVIDENCE_INVALID | employeeId and evidence { datatype, id } are required | A required field is missing. | — |\n| `404` | EMPLOYEE_NOT_FOUND | Employee not found | No employee has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"employeeId":"EMP-1043","requirementId":"RULE-7","kind":"form","evidence":{"datatype":"gov_form","id":"w4"}}}}}}},"/business-made/readiness/point-in-time":{"get":{"operationId":"ReadinessController_pointInTime","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"at","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"location","in":"query","required":false,"schema":{"type":"string"}},{"name":"issuesOnly","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ at|from|to, rows }","content":{"application/json":{"example":{"at":"2026-03-14T19:00:00.000Z","worked":18,"nonCompliant":1,"rows":[{"employeeId":"EMP-1043","name":"Luis Ramirez","shiftId":"sh-1","start":"2026-03-14T16:00:00.000Z","end":"2026-03-15T00:00:00.000Z","station":"bar","requirementId":"RULE-2","title":"Alcohol server","status":"expired","override":{"approverName":"Maya","reason":"renewal booked"}}]}}}},"400":{"description":"Pass `at`, or `from` and `to` (ISO date-times) — Neither form is given, `to` is before `from`, or the window is longer than 62 days.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Pass `at`, or `from` and `to` (ISO date-times)","path":"/business-made/readiness/point-in-time","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Who worked, and were they compliant","description":"The ledger joined with shifts and clock punches. `at` = one instant (every applicable requirement is listed); `from`+`to` = a window of up to 62 days (only overdue/expired rows unless `issuesOnly=false`; each row shows the worst status held during the worked interval). Unscheduled punches are included.\n\n#### Signature\n\n```http\nGET /business-made/readiness/point-in-time (at: string, from: string, to: string, location: string, issuesOnly: string) -> { at\\|from\\|to, rows }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | BAD_WINDOW | Pass `at`, or `from` and `to` (ISO date-times) | Neither form is given, `to` is before `from`, or the window is longer than 62 days. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/export.csv":{"get":{"operationId":"ReadinessController_exportCsv","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"location","in":"query","required":false,"schema":{"type":"string"}},{"name":"at","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"department","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"requirement","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"employee","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"CSV file"},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Inspector export (CSV)","description":"One row per person and requirement at a location, filtered like the matrix (department, status, requirement, search, employee). With no `at` it is the live state; a past `at` is read from the ledger as it stood then. With `from` and `to` it exports the point-in-time rows (who worked, which requirement, its status, any override) instead. Includes overrides/waivers with approver and the rule citation.\n\n#### Signature\n\n```http\nGET /business-made/readiness/export.csv (location: string, at: string, from: string, to: string, department: string, status: string, requirement: string, search: string, employee: string) -> CSV file\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/packs":{"get":{"operationId":"ReadinessController_packs","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"Packs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Jurisdiction packs","description":"Rule templates, each with owner, review date, citation, and notes quoting the official source with its URL. `applied` = all its rules exist in this org; `updateAvailable` = the org has an older version. Harassment prevention: us-ca (5+ employees incl. contractors, 2h/1h within 6 months, every 2 years; seasonal/temporary within 30 days or 100 hours worked), us-ny (annual), us-ny-nyc (15+ employees, more than 80 hours in a calendar year and 90 days, interns and contractors, once per calendar year), us-il (every calendar year by 31 December; restaurants and bars supplement and week-one policy; Chicago 1h/2h plus 1h bystander every July–June year and week-one policy), us-ct (3+ employees, 2h within 6 months, every 10 years), us-me (15+ employees, within 1 year, supervisors additional), us-de (50+ employees, within 1 year then every 2 years, after 6 months of service). Food safety: us-ca (card 30 days/3 years, CFPM), us-ny-nyc (Food Protection Certificate), us-il (handler 30 days/3 years, CFPM, allergen, Chicago sanitation certificate), us-tx (handler 30 days/2 years, CFM 5 years), us-fl (60 days/3 years, CFPM 5 years), us-wa (14 days, 2 years), us-or (30 days/3 years), us-ut (30 days/3 years), us-nm (30 days/3 years, CFPM), us-az-maricopa (30 days/3 years, CFPM). Rules never carry content: the company attaches its own course, certification or policy.\n\n#### Signature\n\n```http\nGET /business-made/readiness/packs () -> Packs\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"]}},"/business-made/readiness/packs/{packId}/apply":{"post":{"operationId":"ReadinessController_applyPack","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"packId","required":true,"in":"path","schema":{"type":"string"},"example":"us-ca"}],"responses":{"201":{"description":"{ packId, version, created, skipped, next }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Unknown jurisdiction pack — No pack has that id; the body lists the `packs` there are.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Unknown jurisdiction pack","path":"/business-made/readiness/packs/{packId}/apply","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Apply a jurisdiction pack","description":"Creates the pack rules for the org as drafts with no course attached (the company attaches its own content, narrows the scope where the notes say so, then activates each rule), skipping codes already present. `{ activate: true }` creates them active; `{ codes: [] }` picks some.\n\n#### Signature\n\n```http\nPOST /business-made/readiness/packs/{packId}/apply (packId: string, body) -> { packId, version, created, skipped, next }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | PACK_NOT_FOUND | Unknown jurisdiction pack | No pack has that id; the body lists the `packs` there are. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made · Readiness"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/staff-portal/training/courses":{"get":{"operationId":"StaffTrainingController_myCourses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ items: MyCourse[], summary: { total, attention, <state>: n }, mode: \"app\" | \"link\" }","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Handle for POST /staff-portal/training/courses/launch"},"requirementId":{"type":"string"},"enrollmentId":{"type":"string"},"courseId":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"provider":{"type":"string"},"durationMinutes":{"type":"number"},"state":{"type":"string","enum":["assigned","in_progress","due_soon","overdue","completed","expiring","expired","covered","waived"]},"stateLabel":{"type":"string"},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"attention":{"type":"boolean","description":"Needs doing or redoing — what the dashboard card shows"},"launch":{"type":"object","nullable":true,"properties":{"label":{"type":"string","enum":["Start","Resume","Start again","Open"]}}},"inApp":{"type":"boolean","description":"Own-content course, taken in My training"}}}},"summary":{"type":"object","additionalProperties":true},"mode":{"type":"string","enum":["app","link"]}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"My courses","description":"The caller’s courses — from their requirements and from direct assignments — most urgent first. State, due date and expiry are computed by the server (assigned, in_progress, due_soon ≤14 days, overdue, completed, expiring ≤30 days or renewal open, expired, covered, waived). `attention` marks what the dashboard shows. `launch` is set for courses taken at a link.\n\n#### Signature\n\n```http\nGET /staff-portal/training/courses () -> { items: MyCourse[], summary: { total, attention, <state>: n }, mode: \"app\" \\| \"link\" }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/training/courses/launch":{"post":{"operationId":"StaffTrainingController_launch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ url, item: MyCourse }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"key is required — `key` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"key is required","path":"/staff-portal/training/courses/launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"That is not one of your courses — Unknown key.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"That is not one of your courses","path":"/staff-portal/training/courses/launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This course has no link yet. Ask HR. — No launchUrl and no course-app link.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This course has no link yet. Ask HR.","path":"/staff-portal/training/courses/launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Start / resume a course","description":"Opens the caller’s enrollment (a fresh one when the completion is expiring or expired), marks it in progress, and returns where to go: the course app’s per-learner start/resume link when the app is configured, else the course’s own launchUrl. Completion is not self-reported — HR marks it, or the course app reports it. For a course backed by a Content Studio post the caller is enrolled on the course engine (a fresh post_progress when renewing) and `url` is the site’s `content-player` page for that post (`<page>/<post slug>`); completion then comes from the post_progress.\n\n#### Signature\n\n```http\nPOST /staff-portal/training/courses/launch (body) -> { url, item: MyCourse }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | NOT_YOUR_COURSE | That is not one of your courses | Unknown key. | — |\n| `409` | NO_LAUNCH_LINK | This course has no link yet. Ask HR. | No launchUrl and no course-app link. | — |\n| `400` | KEY_REQUIRED | key is required | `key` is missing. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"}}},"example":{"key":"req:66f1c0ffee"}}}}}},"/staff-portal/training":{"get":{"operationId":"StaffTrainingController_mine","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ items }","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"requirementId":{"type":"string"},"kind":{"type":"string","enum":["course","policy","signoff","certification"]},"title":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["compliant","due_soon","overdue","expired","waived","not_required"]},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"courseUrl":{"type":"string"},"policyId":{"type":"string"},"acknowledgementId":{"type":"string"},"content":{"type":"object","description":"When `source` is post: where the person stands on the course post, from their post_progress.","properties":{"source":{"type":"string","enum":["post","own","link","marketplace"]},"courseId":{"type":"string","description":"bm_course sk"},"postId":{"type":"string","description":"sk of the Content Studio course post"},"title":{"type":"string"},"description":{"type":"string"},"durationMinutes":{"type":"number"},"passingScore":{"type":"number","description":"The requirement rule’s evidence.minScore"},"progress":{"type":"object","properties":{"status":{"type":"string","nullable":true,"enum":["active","completed","withdrawn",null]},"percent":{"type":"number"},"score":{"type":"number","description":"Marked by the server against the pages’ answer keys"},"completedAt":{"type":"string"},"reason":{"type":"string","description":"Why a completed course does not count yet (for example the score is under the pass mark, or the post no longer exists)"}}}}},"enrollmentStatus":{"type":"string","description":"For a post-backed course: enrolled, in-progress, completed or withdrawn, from the post_progress"},"actions":{"type":"array","items":{"type":"string","enum":["start","complete","acknowledge","open_course","upload","ask_for_signoff"]}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"My training","description":"The caller’s courses, policies, floor sign-offs and certifications from the readiness engine, most urgent first, each with the content to show and the actions available now.\n\n#### Signature\n\n```http\nGET /staff-portal/training () -> { items }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/training/at-clock-in":{"get":{"operationId":"StaffTrainingController_atClockIn","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"station","required":false,"in":"query","schema":{"type":"string"},"description":"Station about to be worked (matches bm_schedule.station)."}],"responses":{"200":{"description":"{ station, effect, overrideActive, required: [TrainingItem & { effect, completableNow, estimatedMinutes }] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Training required at clock-in","description":"What the clock-in gate says for this station (block / warn) plus station-scoped items coming due. `completableNow` marks short own-content courses and policies that can be done at the clock.\n\n#### Signature\n\n```http\nGET /staff-portal/training/at-clock-in (station?: string) -> { station, effect, overrideActive, required: [TrainingItem & { effect, completableNow, estimatedMinutes }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/training/{requirementId}/start":{"post":{"operationId":"StaffTrainingController_start","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requirementId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_requirement_rule sk — must be one of the caller’s own requirements.","example":"66f1c0ffee"}],"responses":{"201":{"description":"{ item }","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"object","properties":{"requirementId":{"type":"string"},"kind":{"type":"string","enum":["course","policy","signoff","certification"]},"title":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["compliant","due_soon","overdue","expired","waived","not_required"]},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"courseUrl":{"type":"string"},"policyId":{"type":"string"},"acknowledgementId":{"type":"string"},"content":{"type":"object","description":"When `source` is post: where the person stands on the course post, from their post_progress.","properties":{"source":{"type":"string","enum":["post","own","link","marketplace"]},"courseId":{"type":"string","description":"bm_course sk"},"postId":{"type":"string","description":"sk of the Content Studio course post"},"title":{"type":"string"},"description":{"type":"string"},"durationMinutes":{"type":"number"},"passingScore":{"type":"number","description":"The requirement rule’s evidence.minScore"},"progress":{"type":"object","properties":{"status":{"type":"string","nullable":true,"enum":["active","completed","withdrawn",null]},"percent":{"type":"number"},"score":{"type":"number","description":"Marked by the server against the pages’ answer keys"},"completedAt":{"type":"string"},"reason":{"type":"string","description":"Why a completed course does not count yet (for example the score is under the pass mark, or the post no longer exists)"}}}}},"enrollmentStatus":{"type":"string","description":"For a post-backed course: enrolled, in-progress, completed or withdrawn, from the post_progress"},"actions":{"type":"array","items":{"type":"string","enum":["start","complete","acknowledge","open_course","upload","ask_for_signoff"]}}}}}}}}},"400":{"description":"A policy requirement is not started — acknowledge it — The requirement is not a course.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A policy requirement is not started — acknowledge it","path":"/staff-portal/training/{requirementId}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The Content Studio course linked to this training no longer exists. Ask HR. — The bm_course’s postId names a post that was deleted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"The Content Studio course linked to this training no longer exists. Ask HR.","path":"/staff-portal/training/{requirementId}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This course is taken at its link — A link or course-app course: use POST /staff-portal/training/courses/launch (MARKETPLACE_COURSE for course-app courses).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This course is taken at its link","path":"/staff-portal/training/{requirementId}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Start a course","description":"Creates the caller’s enrollment for the requirement (a fresh one for a renewal) and marks it in progress. Own-content courses only; marketplace courses open in the course app. For a course backed by a Content Studio post (`content.source` = post) it enrolls the caller on the course engine once (their post_progress) and returns the item; the course itself is opened from POST /staff-portal/training/courses/launch.\n\n#### Signature\n\n```http\nPOST /staff-portal/training/{requirementId}/start (requirementId: string) -> { item }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | POST_NOT_FOUND | The Content Studio course linked to this training no longer exists. Ask HR. | The bm_course’s postId names a post that was deleted. | — |\n| `409` | LINK_COURSE | This course is taken at its link | A link or course-app course: use POST /staff-portal/training/courses/launch (MARKETPLACE_COURSE for course-app courses). | — |\n| `400` | NOT_A_COURSE | A policy requirement is not started — acknowledge it | The requirement is not a course. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/training/{requirementId}/complete":{"post":{"operationId":"StaffTrainingController_complete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requirementId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_requirement_rule sk — must be one of the caller’s own requirements.","example":"66f1c0ffee"}],"responses":{"201":{"description":"{ item }","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"object","properties":{"requirementId":{"type":"string"},"kind":{"type":"string","enum":["course","policy","signoff","certification"]},"title":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["compliant","due_soon","overdue","expired","waived","not_required"]},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"courseUrl":{"type":"string"},"policyId":{"type":"string"},"acknowledgementId":{"type":"string"},"content":{"type":"object","description":"When `source` is post: where the person stands on the course post, from their post_progress.","properties":{"source":{"type":"string","enum":["post","own","link","marketplace"]},"courseId":{"type":"string","description":"bm_course sk"},"postId":{"type":"string","description":"sk of the Content Studio course post"},"title":{"type":"string"},"description":{"type":"string"},"durationMinutes":{"type":"number"},"passingScore":{"type":"number","description":"The requirement rule’s evidence.minScore"},"progress":{"type":"object","properties":{"status":{"type":"string","nullable":true,"enum":["active","completed","withdrawn",null]},"percent":{"type":"number"},"score":{"type":"number","description":"Marked by the server against the pages’ answer keys"},"completedAt":{"type":"string"},"reason":{"type":"string","description":"Why a completed course does not count yet (for example the score is under the pass mark, or the post no longer exists)"}}}}},"enrollmentStatus":{"type":"string","description":"For a post-backed course: enrolled, in-progress, completed or withdrawn, from the post_progress"},"actions":{"type":"array","items":{"type":"string","enum":["start","complete","acknowledge","open_course","upload","ask_for_signoff"]}}}}}}}}},"400":{"description":"Only a course is completed here — The requirement is a policy, sign-off or certification.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a course is completed here","path":"/staff-portal/training/{requirementId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"This course records your progress as you go — finishing it in the course is all it takes. — The course is backed by a Content Studio post: it completes itself in the course player.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This course records your progress as you go — finishing it in the course is all it takes.","path":"/staff-portal/training/{requirementId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"Score below the pass mark — score < passingScore; the attempt is recorded.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Score below the pass mark","path":"/staff-portal/training/{requirementId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Complete a course","description":"Completes the caller’s in-progress enrollment. Courses with an assessment need `score` at or above the pass mark (rule evidence.minScore, else the course’s). Records expiry from the rule’s renewal and hands the enrollment to the readiness engine as evidence.\n\n#### Signature\n\n```http\nPOST /staff-portal/training/{requirementId}/complete (requirementId: string, body) -> { item }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Notes\n\n- A course backed by a Content Studio post is never completed here: it completes when the caller finishes it in the course player, and its score is marked by the server.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | ASSESSMENT_FAILED | Score below the pass mark | score < passingScore; the attempt is recorded. | — |\n| `409` | POST_COURSE | This course records your progress as you go — finishing it in the course is all it takes. | The course is backed by a Content Studio post: it completes itself in the course player. | — |\n| `400` | NOT_A_COURSE | Only a course is completed here | The requirement is a policy, sign-off or certification. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"score":{"type":"number"},"timeSpentMinutes":{"type":"number"}}},"example":{"score":90}}}}}},"/staff-portal/training/{requirementId}/acknowledge":{"post":{"operationId":"StaffTrainingController_acknowledge","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requirementId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_requirement_rule sk — must be one of the caller’s own requirements.","example":"66f1c0ffee"}],"responses":{"201":{"description":"{ item }","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"object","properties":{"requirementId":{"type":"string"},"kind":{"type":"string","enum":["course","policy","signoff","certification"]},"title":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["compliant","due_soon","overdue","expired","waived","not_required"]},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"courseUrl":{"type":"string"},"policyId":{"type":"string"},"acknowledgementId":{"type":"string"},"content":{"type":"object","description":"When `source` is post: where the person stands on the course post, from their post_progress.","properties":{"source":{"type":"string","enum":["post","own","link","marketplace"]},"courseId":{"type":"string","description":"bm_course sk"},"postId":{"type":"string","description":"sk of the Content Studio course post"},"title":{"type":"string"},"description":{"type":"string"},"durationMinutes":{"type":"number"},"passingScore":{"type":"number","description":"The requirement rule’s evidence.minScore"},"progress":{"type":"object","properties":{"status":{"type":"string","nullable":true,"enum":["active","completed","withdrawn",null]},"percent":{"type":"number"},"score":{"type":"number","description":"Marked by the server against the pages’ answer keys"},"completedAt":{"type":"string"},"reason":{"type":"string","description":"Why a completed course does not count yet (for example the score is under the pass mark, or the post no longer exists)"}}}}},"enrollmentStatus":{"type":"string","description":"For a post-backed course: enrolled, in-progress, completed or withdrawn, from the post_progress"},"actions":{"type":"array","items":{"type":"string","enum":["start","complete","acknowledge","open_course","upload","ask_for_signoff"]}}}}}}}}},"400":{"description":"The signature is incomplete — No consent, or neither a typed name nor an image.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The signature is incomplete","path":"/staff-portal/training/{requirementId}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"No policy is attached to this requirement yet — The policy rule names no policy.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"No policy is attached to this requirement yet","path":"/staff-portal/training/{requirementId}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Acknowledge a policy","description":"E-signs the current version of the requirement’s policy. Signature time, IP and user agent are stamped by the server.\n\n#### Signature\n\n```http\nPOST /staff-portal/training/{requirementId}/acknowledge (requirementId: string, body) -> { item }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | SIGNATURE_INVALID | The signature is incomplete | No consent, or neither a typed name nor an image. | — |\n| `409` | NO_POLICY_ATTACHED | No policy is attached to this requirement yet | The policy rule names no policy. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"signature":{"type":"object","properties":{"typedName":{"type":"string"},"image":{"type":"string","description":"PNG/JPEG/SVG data URL"},"consent":{"type":"boolean","description":"Must be true"}}}}},"example":{"signature":{"typedName":"Ana Ruiz","consent":true}}}}}}},"/staff-portal/training/qr-session":{"post":{"operationId":"StaffTrainingController_qrSession","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ session: { token, expiresAt, via, station, employee: { id, name } }, items, qr?: { code, expiresAt, path } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"orgid header is required — No `orgid` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"orgid header is required","path":"/staff-portal/training/qr-session","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Scan a training code, sign in at the kiosk, or sign in — No code, kiosk sign-in or login.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Scan a training code, sign in at the kiosk, or sign in","path":"/staff-portal/training/qr-session","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"403":{"description":"Only a manager or admin can create a code for someone else — Creating a code for another person without a manager or admin role.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only a manager or admin can create a code for someone else","path":"/staff-portal/training/qr-session","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Start a training session (QR / kiosk)","description":"Issues a 30-minute training-only session. `{ code }` redeems a one-time QR code; `{ employeeId, pin?, cardUid? }` signs in at a kiosk exactly like quick-card sign-in; signed in with no body = a session for yourself plus a QR code (10 minutes, single use) to continue on another device. An owner/admin may send `{ forEmployeeId }` to get a code for someone else.\n\n#### Signature\n\n```http\nPOST /staff-portal/training/qr-session (body) -> { session: { token, expiresAt, via, station, employee: { id, name } }, items, qr?: { code, expiresAt, path } }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORG_REQUIRED | orgid header is required | No `orgid` header. | — |\n| `401` | TRAINING_SESSION_REQUIRED | Scan a training code, sign in at the kiosk, or sign in | No code, kiosk sign-in or login. | — |\n| `403` | FORBIDDEN | Only a manager or admin can create a code for someone else | Creating a code for another person without a manager or admin role. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"employeeId":{"type":"string"},"pin":{"type":"string"},"cardUid":{"type":"string"},"station":{"type":"string"},"forEmployeeId":{"type":"string"}}}}}}}},"/staff-portal/training/session":{"get":{"operationId":"StaffTrainingController_sessionItems","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-training-session","required":true,"in":"header","schema":{"type":"string"},"description":"Token from POST /staff-portal/training/qr-session."}],"responses":{"200":{"description":"{ session, items }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Training session required — No `x-training-session` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Training session required","path":"/staff-portal/training/session","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"My training (session)","description":"Same as GET /staff-portal/training for the session holder.\n\n#### Signature\n\n```http\nGET /staff-portal/training/session () -> { session, items }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | TRAINING_SESSION_REQUIRED | Training session required | No `x-training-session` header. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/staff-portal/training/session/at-clock-in":{"get":{"operationId":"StaffTrainingController_sessionAtClockIn","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-training-session","required":true,"in":"header","schema":{"type":"string"},"description":"Token from POST /staff-portal/training/qr-session."},{"name":"station","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"As GET /staff-portal/training/at-clock-in","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Training session required — No `x-training-session` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Training session required","path":"/staff-portal/training/session/at-clock-in","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"At-clock-in (session)","description":"#### Signature\n\n```http\nGET /staff-portal/training/session/at-clock-in (station?: string) -> As GET /staff-portal/training/at-clock-in\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | TRAINING_SESSION_REQUIRED | Training session required | No `x-training-session` header. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/staff-portal/training/session/end":{"post":{"operationId":"StaffTrainingController_endSession","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-training-session","required":true,"in":"header","schema":{"type":"string"},"description":"Token from POST /staff-portal/training/qr-session."}],"responses":{"201":{"description":"{ ended: true }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Training session required — No `x-training-session` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Training session required","path":"/staff-portal/training/session/end","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"End the training session","description":"#### Signature\n\n```http\nPOST /staff-portal/training/session/end () -> { ended: true }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | TRAINING_SESSION_REQUIRED | Training session required | No `x-training-session` header. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/staff-portal/training/session/courses/launch":{"post":{"operationId":"StaffTrainingController_sessionLaunch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-training-session","required":false,"in":"header","schema":{"type":"string"},"description":"Token from POST /staff-portal/training/qr-session (or send it as body.sessionToken)."}],"responses":{"201":{"description":"{ url, item }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"key is required — `key` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"key is required","path":"/staff-portal/training/session/courses/launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Training session required — No `x-training-session` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Training session required","path":"/staff-portal/training/session/courses/launch","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Start / resume a course (session)","description":"Kiosk / QR version of POST /staff-portal/training/courses/launch, for the session holder only. `key` is the item key `req:<requirementId>` (or an `enr:<enrollmentId>` from My courses).\n\n#### Signature\n\n```http\nPOST /staff-portal/training/session/courses/launch (body) -> { url, item }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | TRAINING_SESSION_REQUIRED | Training session required | No `x-training-session` header. | — |\n| `400` | KEY_REQUIRED | key is required | `key` is missing. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"sessionToken":{"type":"string"}}}}}}}},"/staff-portal/training/session/{requirementId}/start":{"post":{"operationId":"StaffTrainingController_sessionStart","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-training-session","required":true,"in":"header","schema":{"type":"string"},"description":"Token from POST /staff-portal/training/qr-session."},{"name":"requirementId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_requirement_rule sk — must be one of the caller’s own requirements.","example":"66f1c0ffee"}],"responses":{"201":{"description":"{ item }","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"object","properties":{"requirementId":{"type":"string"},"kind":{"type":"string","enum":["course","policy","signoff","certification"]},"title":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["compliant","due_soon","overdue","expired","waived","not_required"]},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"courseUrl":{"type":"string"},"policyId":{"type":"string"},"acknowledgementId":{"type":"string"},"content":{"type":"object","description":"When `source` is post: where the person stands on the course post, from their post_progress.","properties":{"source":{"type":"string","enum":["post","own","link","marketplace"]},"courseId":{"type":"string","description":"bm_course sk"},"postId":{"type":"string","description":"sk of the Content Studio course post"},"title":{"type":"string"},"description":{"type":"string"},"durationMinutes":{"type":"number"},"passingScore":{"type":"number","description":"The requirement rule’s evidence.minScore"},"progress":{"type":"object","properties":{"status":{"type":"string","nullable":true,"enum":["active","completed","withdrawn",null]},"percent":{"type":"number"},"score":{"type":"number","description":"Marked by the server against the pages’ answer keys"},"completedAt":{"type":"string"},"reason":{"type":"string","description":"Why a completed course does not count yet (for example the score is under the pass mark, or the post no longer exists)"}}}}},"enrollmentStatus":{"type":"string","description":"For a post-backed course: enrolled, in-progress, completed or withdrawn, from the post_progress"},"actions":{"type":"array","items":{"type":"string","enum":["start","complete","acknowledge","open_course","upload","ask_for_signoff"]}}}}}}}}},"400":{"description":"A policy requirement is not started — acknowledge it — The requirement is not a course.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"A policy requirement is not started — acknowledge it","path":"/staff-portal/training/session/{requirementId}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Training session required — No `x-training-session` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Training session required","path":"/staff-portal/training/session/{requirementId}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"No course is attached to this requirement yet — The course rule names no course.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"No course is attached to this requirement yet","path":"/staff-portal/training/session/{requirementId}/start","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Start a course (session)","description":"#### Signature\n\n```http\nPOST /staff-portal/training/session/{requirementId}/start (requirementId: string) -> { item }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | TRAINING_SESSION_REQUIRED | Training session required | No `x-training-session` header. | — |\n| `400` | NOT_A_COURSE | A policy requirement is not started — acknowledge it | The requirement is not a course. | — |\n| `409` | NO_COURSE_ATTACHED | No course is attached to this requirement yet | The course rule names no course. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/staff-portal/training/session/{requirementId}/complete":{"post":{"operationId":"StaffTrainingController_sessionComplete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-training-session","required":true,"in":"header","schema":{"type":"string"},"description":"Token from POST /staff-portal/training/qr-session."},{"name":"requirementId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_requirement_rule sk — must be one of the caller’s own requirements.","example":"66f1c0ffee"}],"responses":{"201":{"description":"{ item }","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"object","properties":{"requirementId":{"type":"string"},"kind":{"type":"string","enum":["course","policy","signoff","certification"]},"title":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["compliant","due_soon","overdue","expired","waived","not_required"]},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"courseUrl":{"type":"string"},"policyId":{"type":"string"},"acknowledgementId":{"type":"string"},"content":{"type":"object","description":"When `source` is post: where the person stands on the course post, from their post_progress.","properties":{"source":{"type":"string","enum":["post","own","link","marketplace"]},"courseId":{"type":"string","description":"bm_course sk"},"postId":{"type":"string","description":"sk of the Content Studio course post"},"title":{"type":"string"},"description":{"type":"string"},"durationMinutes":{"type":"number"},"passingScore":{"type":"number","description":"The requirement rule’s evidence.minScore"},"progress":{"type":"object","properties":{"status":{"type":"string","nullable":true,"enum":["active","completed","withdrawn",null]},"percent":{"type":"number"},"score":{"type":"number","description":"Marked by the server against the pages’ answer keys"},"completedAt":{"type":"string"},"reason":{"type":"string","description":"Why a completed course does not count yet (for example the score is under the pass mark, or the post no longer exists)"}}}}},"enrollmentStatus":{"type":"string","description":"For a post-backed course: enrolled, in-progress, completed or withdrawn, from the post_progress"},"actions":{"type":"array","items":{"type":"string","enum":["start","complete","acknowledge","open_course","upload","ask_for_signoff"]}}}}}}}}},"400":{"description":"Only a course is completed here — The requirement is a policy, sign-off or certification.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a course is completed here","path":"/staff-portal/training/session/{requirementId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Training session required — No `x-training-session` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Training session required","path":"/staff-portal/training/session/{requirementId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Start the course first — There is no in-progress enrollment.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Start the course first","path":"/staff-portal/training/session/{requirementId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Complete a course (session)","description":"#### Signature\n\n```http\nPOST /staff-portal/training/session/{requirementId}/complete (requirementId: string) -> { item }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | TRAINING_SESSION_REQUIRED | Training session required | No `x-training-session` header. | — |\n| `400` | NOT_A_COURSE | Only a course is completed here | The requirement is a policy, sign-off or certification. | — |\n| `409` | NOT_STARTED | Start the course first | There is no in-progress enrollment. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/staff-portal/training/session/{requirementId}/acknowledge":{"post":{"operationId":"StaffTrainingController_sessionAcknowledge","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-training-session","required":true,"in":"header","schema":{"type":"string"},"description":"Token from POST /staff-portal/training/qr-session."},{"name":"requirementId","required":true,"in":"path","schema":{"type":"string"},"description":"bm_requirement_rule sk — must be one of the caller’s own requirements.","example":"66f1c0ffee"}],"responses":{"201":{"description":"{ item }","content":{"application/json":{"schema":{"type":"object","properties":{"item":{"type":"object","properties":{"requirementId":{"type":"string"},"kind":{"type":"string","enum":["course","policy","signoff","certification"]},"title":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["compliant","due_soon","overdue","expired","waived","not_required"]},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"courseUrl":{"type":"string"},"policyId":{"type":"string"},"acknowledgementId":{"type":"string"},"content":{"type":"object","description":"When `source` is post: where the person stands on the course post, from their post_progress.","properties":{"source":{"type":"string","enum":["post","own","link","marketplace"]},"courseId":{"type":"string","description":"bm_course sk"},"postId":{"type":"string","description":"sk of the Content Studio course post"},"title":{"type":"string"},"description":{"type":"string"},"durationMinutes":{"type":"number"},"passingScore":{"type":"number","description":"The requirement rule’s evidence.minScore"},"progress":{"type":"object","properties":{"status":{"type":"string","nullable":true,"enum":["active","completed","withdrawn",null]},"percent":{"type":"number"},"score":{"type":"number","description":"Marked by the server against the pages’ answer keys"},"completedAt":{"type":"string"},"reason":{"type":"string","description":"Why a completed course does not count yet (for example the score is under the pass mark, or the post no longer exists)"}}}}},"enrollmentStatus":{"type":"string","description":"For a post-backed course: enrolled, in-progress, completed or withdrawn, from the post_progress"},"actions":{"type":"array","items":{"type":"string","enum":["start","complete","acknowledge","open_course","upload","ask_for_signoff"]}}}}}}}}},"400":{"description":"Only a policy is acknowledged — The requirement is not a policy.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a policy is acknowledged","path":"/staff-portal/training/session/{requirementId}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Training session required — No `x-training-session` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Training session required","path":"/staff-portal/training/session/{requirementId}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"No policy is attached to this requirement yet — The policy rule names no policy.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"No policy is attached to this requirement yet","path":"/staff-portal/training/session/{requirementId}/acknowledge","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Training"],"summary":"Acknowledge a policy (session)","description":"#### Signature\n\n```http\nPOST /staff-portal/training/session/{requirementId}/acknowledge (requirementId: string) -> { item }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `401` | TRAINING_SESSION_REQUIRED | Training session required | No `x-training-session` header. | — |\n| `400` | NOT_A_POLICY | Only a policy is acknowledged | The requirement is not a policy. | — |\n| `409` | NO_POLICY_ATTACHED | No policy is attached to this requirement yet | The policy rule names no policy. | — |\n\nPlus the standard platform errors: `429`, `500`."}},"/business-made/training/courses":{"get":{"operationId":"TrainingController_listCourses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ courseApp: { connected, mode, provider?, reason? }, data: [{ id, title, description, category, source (post | own | link | marketplace), postId, isLink, launchUrl, provider, durationMinutes, renewalMonths, status, requirements, counts }], total }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Courses with roster counts","description":"Every course with its requirement rules and per-state counts, plus how courses launch now (`courseApp.mode` app | link).\n\n#### Signature\n\n```http\nGET /business-made/training/courses (search?: string, status?: string) -> { courseApp: { connected, mode, provider?, reason? }, data: [{ id, title, description, category, source (post \\| own \\| link \\| marketplace), postId, isLink, launchUrl, provider, durationMinutes, renewalMonths, status, requirements, counts }], total }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/training/courses/link":{"post":{"operationId":"TrainingController_createLinkCourse","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The course view","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"title is required; launchUrl must be an http(s) link — Missing title, neither launchUrl nor postId, bad link, a postId that names no post (“postId does not name a Content Studio course”), negative numbers, unknown category.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"title is required; launchUrl must be an http(s) link","path":"/business-made/training/courses/link","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Add a link course","description":"Creates a bm_course with source = link, status active. `durationMinutes` is stored as duration.hours. `renewalMonths` sets the expiry of each completion when no requirement rule sets one. Give either `launchUrl` (taken elsewhere) or `postId` — a Content Studio course post, taken in the site’s course player, whose completion and score come only from each person’s post_progress. With `postId`, `launchUrl` is optional.\n\n#### Signature\n\n```http\nPOST /business-made/training/courses/link (body) -> The course view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | COURSE_INVALID | title is required; launchUrl must be an http(s) link | Missing title, neither launchUrl nor postId, bad link, a postId that names no post (“postId does not name a Content Studio course”), negative numbers, unknown category. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"launchUrl":{"type":"string"},"postId":{"type":"string","description":"sk of a Content Studio course post"},"durationMinutes":{"type":"number"},"renewalMonths":{"type":"number"},"category":{"type":"string"},"description":{"type":"string"},"providerName":{"type":"string"}}},"examples":{"link":{"summary":"Taken at a link","value":{"title":"Food handler basics","launchUrl":"https://learn.example.com/food-handler","durationMinutes":45,"renewalMonths":36,"category":"safety"}},"post":{"summary":"Backed by a Content Studio course post","value":{"title":"Allergen awareness","postId":"66f2a0c0ffee","durationMinutes":30,"renewalMonths":12,"category":"safety"}}}}}}}},"/business-made/training/courses/link/{id}":{"put":{"operationId":"TrainingController_updateLinkCourse","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk"}],"responses":{"200":{"description":"The course view","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No bm_course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/courses/link/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Change a link course","description":"#### Signature\n\n```http\nPUT /business-made/training/courses/link/{id} (id: string, body) -> The course view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No bm_course has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/business-made/training/courses/{id}/roster":{"get":{"operationId":"TrainingController_roster","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk"},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"location","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"0-based","schema":{"type":"integer"}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"{ course, states: [{ value, label, count }], counts, rows, total, page, pageSize, courseApp }","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","properties":{"employeeId":{"type":"string"},"name":{"type":"string"},"location":{"type":"string"},"department":{"type":"string"},"position":{"type":"string"},"source":{"type":"string","enum":["requirement","assigned"]},"requirements":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}}}},"state":{"type":"string","enum":["assigned","in_progress","due_soon","overdue","completed","expiring","expired","covered","waived"]},"stateLabel":{"type":"string"},"readinessStatus":{"type":"string"},"dueDate":{"type":"string"},"completedAt":{"type":"string"},"expiresAt":{"type":"string"},"enrollmentId":{"type":"string"},"completion":{"type":"object","nullable":true,"properties":{"source":{"type":"string","enum":["manual","course_app","self"]},"by":{"type":"string"},"note":{"type":"string"},"score":{"type":"number"},"certificate":{"type":"object","additionalProperties":true}}},"actions":{"type":"array","items":{"type":"string","enum":["mark_completed","mark_not_completed"]}}}}}}}}}},"400":{"description":"state must be one of assigned, in_progress, … — `state` is not a course state.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"state must be one of assigned, in_progress, …","path":"/business-made/training/courses/{id}/roster","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No bm_course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/courses/{id}/roster","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Course roster","description":"Everyone the course applies to (through a requirement rule or a direct assignment) with state, due, completion and expiry, worst first. Rows carry the actions available: mark_completed, mark_not_completed.\n\n#### Signature\n\n```http\nGET /business-made/training/courses/{id}/roster (id: string, state?: string, search?: string, location?: string, page?: integer, pageSize?: integer) -> { course, states: [{ value, label, count }], counts, rows, total, page, pageSize, courseApp }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No bm_course has that id. | — |\n| `400` | BAD_FILTER | state must be one of assigned, in_progress, … | `state` is not a course state. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/training/courses/{id}/complete":{"post":{"operationId":"TrainingController_markCompleted","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk"}],"responses":{"201":{"description":"{ courseId, updated, failed, results }","content":{"application/json":{"schema":{"type":"object","properties":{"courseId":{"type":"string"},"updated":{"type":"number"},"failed":{"type":"number"},"results":{"type":"array","items":{"type":"object","properties":{"employeeId":{"type":"string"},"ok":{"type":"boolean"},"enrollmentId":{"type":"string"},"expiresAt":{"type":"string"},"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"400":{"description":"employeeIds is required — `employeeIds` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"employeeIds is required","path":"/business-made/training/courses/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or the location manager can record a completion — The caller is neither (or is marking their own training: \"Nobody can mark their own training completed\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or the location manager can record a completion","path":"/business-made/training/courses/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Course not found — No bm_course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/courses/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This is a Content Studio course: completion and score come only from the course itself. Review or approve the person’s work there. — The course is backed by a Content Studio post (postId). Review the person’s work under /content-studio instead.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This is a Content Studio course: completion and score come only from the course itself. Review or approve the person’s work there.","path":"/business-made/training/courses/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"Score is below the pass mark — Per person, in results: score < the rule’s evidence.minScore.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Score is below the pass mark","path":"/business-made/training/courses/{id}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Mark completed (one or many)","description":"For each person: completes their open enrollment (or creates one), stamps `manualCompletion` and a `completionHistory` entry, sets expiry from the requirement’s renewal else the course’s, records readiness evidence and emits `journeys.evidence`. `completedAt` defaults to now and cannot be in the future. Per-person results — one failure does not stop the rest.\n\n#### Signature\n\n```http\nPOST /business-made/training/courses/{id}/complete (id: string, body) -> { courseId, updated, failed, results }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | SCORE_BELOW_PASS | Score is below the pass mark | Per person, in results: score < the rule’s evidence.minScore. | — |\n| `409` | POST_COURSE | This is a Content Studio course: completion and score come only from the course itself. Review or approve the person’s work there. | The course is backed by a Content Studio post (postId). Review the person’s work under /content-studio instead. | — |\n| `404` | COURSE_NOT_FOUND | Course not found | No bm_course has that id. | — |\n| `403` | NOT_ALLOWED_TO_MARK | Only HR or the location manager can record a completion | The caller is neither (or is marking their own training: \"Nobody can mark their own training completed\"). | — |\n| `400` | EMPLOYEES_REQUIRED | employeeIds is required | `employeeIds` is empty. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"employeeIds":{"type":"array","items":{"type":"string"}},"completedAt":{"type":"string"},"score":{"type":"number"},"note":{"type":"string"},"certificate":{"type":"object","properties":{"fileId":{"type":"string"},"url":{"type":"string"},"path":{"type":"string"},"name":{"type":"string"}}}}},"example":{"employeeIds":["EMP-1043","EMP-1051"],"completedAt":"2026-09-20","note":"Certificate on file"}}}}}},"/business-made/training/courses/{id}/not-completed":{"post":{"operationId":"TrainingController_markNotCompleted","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk"}],"responses":{"201":{"description":"{ courseId, updated, failed, results }","content":{"application/json":{"schema":{"type":"object","properties":{"courseId":{"type":"string"},"updated":{"type":"number"},"failed":{"type":"number"},"results":{"type":"array","items":{"type":"object","properties":{"employeeId":{"type":"string"},"ok":{"type":"boolean"},"enrollmentId":{"type":"string"},"expiresAt":{"type":"string"},"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"400":{"description":"employeeIds is required — `employeeIds` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"employeeIds is required","path":"/business-made/training/courses/{id}/not-completed","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or the location manager can record a completion — The caller is neither (or is marking their own training: \"Nobody can mark their own training completed\").","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or the location manager can record a completion","path":"/business-made/training/courses/{id}/not-completed","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Course not found — No bm_course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/courses/{id}/not-completed","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"has no completion of this course to undo — Per person, in results.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"has no completion of this course to undo","path":"/business-made/training/courses/{id}/not-completed","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Mark not completed (undo)","description":"For each person: reopens their latest completed enrollment. Nothing is deleted — the completion stays in `completionHistory` with who undid it and why (a course-app reference stays there too so a webhook retry is not re-applied), and the readiness ledger gets an `evidence_rejected` entry. Emits `journeys.evidence`.\n\n#### Signature\n\n```http\nPOST /business-made/training/courses/{id}/not-completed (id: string, body) -> { courseId, updated, failed, results }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | NOT_COMPLETED | has no completion of this course to undo | Per person, in results. | — |\n| `404` | COURSE_NOT_FOUND | Course not found | No bm_course has that id. | — |\n| `403` | NOT_ALLOWED_TO_MARK | Only HR or the location manager can record a completion | The caller is neither (or is marking their own training: \"Nobody can mark their own training completed\"). | — |\n| `400` | EMPLOYEES_REQUIRED | employeeIds is required | `employeeIds` is empty. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"employeeIds":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}}}}}}}},"/business-made/training/courses/{id}/assign":{"post":{"operationId":"TrainingController_assign","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_course sk"}],"responses":{"201":{"description":"{ courseId, updated, failed, results }","content":{"application/json":{"schema":{"type":"object","properties":{"courseId":{"type":"string"},"updated":{"type":"number"},"failed":{"type":"number"},"results":{"type":"array","items":{"type":"object","properties":{"employeeId":{"type":"string"},"ok":{"type":"boolean"},"enrollmentId":{"type":"string"},"expiresAt":{"type":"string"},"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"400":{"description":"employeeIds is required — `employeeIds` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"employeeIds is required","path":"/business-made/training/courses/{id}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Course not found — No bm_course has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Course not found","path":"/business-made/training/courses/{id}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"There is no email on file to enrol this person in the course — A post-backed course needs the person's email to enrol them in the Content Studio course.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"There is no email on file to enrol this person in the course","path":"/business-made/training/courses/{id}/assign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Assign people directly","description":"Creates an enrollment (enrollmentType assigned) per person, or updates the due date of an open one. For a course backed by a Content Studio post, each person is also enrolled on the course engine (their post_progress).\n\n#### Signature\n\n```http\nPOST /business-made/training/courses/{id}/assign (id: string, body) -> { courseId, updated, failed, results }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | COURSE_NOT_FOUND | Course not found | No bm_course has that id. | — |\n| `400` | EMPLOYEES_REQUIRED | employeeIds is required | `employeeIds` is empty. | — |\n| `409` | NO_LEARNER_EMAIL | There is no email on file to enrol this person in the course | A post-backed course needs the person's email to enrol them in the Content Studio course. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"employeeIds":{"type":"array","items":{"type":"string"}},"dueDate":{"type":"string"}}}}}}}},"/business-made/training/marketplace/status":{"get":{"operationId":"TrainingController_marketplaceStatus","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ connected, mode: \"app\" | \"link\", provider?, reason? } — link mode (no course app configured) is fully working: courses open from their own launchUrl.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Course app connection","description":"#### Signature\n\n```http\nGET /business-made/training/marketplace/status () -> { connected, mode: \"app\" \\| \"link\", provider?, reason? } — link mode (no course app configured) is fully working: courses open from their own launchUrl.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/training/marketplace/courses":{"get":{"operationId":"TrainingController_marketplaceCourses","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"search","required":false,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"{ connected, reason?, data: [MarketplaceCourse & { attachedCourseId }], total, page, pageSize }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Search the course app","description":"Searches the course app catalogue. Without a configured course app this answers `{ connected: false, reason, data: [] }` — add link courses instead.\n\n#### Signature\n\n```http\nGET /business-made/training/marketplace/courses (search?: string, page?: integer, pageSize?: integer) -> { connected, reason?, data: [MarketplaceCourse & { attachedCourseId }], total, page, pageSize }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/training/marketplace/attach":{"post":{"operationId":"TrainingController_attach","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The bm_course","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"marketplaceCourseId is required — `marketplaceCourseId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"marketplaceCourseId is required","path":"/business-made/training/marketplace/attach","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The course app has no such course — The course app does not know that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"The course app has no such course","path":"/business-made/training/marketplace/attach","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"description":"The course marketplace app is not connected — No course app yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":503,"error":"The course marketplace app is not connected","path":"/business-made/training/marketplace/attach","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Business Made · Training"],"summary":"Attach a marketplace course","description":"Creates (or reuses) a bm_course with source = marketplace and, with requirementId, adds it to that course requirement’s target.courseIds.\n\n#### Signature\n\n```http\nPOST /business-made/training/marketplace/attach (body) -> The bm_course\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `503` | COURSE_APP_NOT_CONNECTED | The course marketplace app is not connected | No course app yet. | — |\n| `400` | MARKETPLACE_COURSE_ID_REQUIRED | marketplaceCourseId is required | `marketplaceCourseId` is missing. | — |\n| `404` | MARKETPLACE_COURSE_NOT_FOUND | The course app has no such course | The course app does not know that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"marketplaceCourseId":{"type":"string"},"requirementId":{"type":"string"},"category":{"type":"string"}}}}}}}},"/business-made/training/marketplace/completion":{"post":{"operationId":"TrainingController_completion","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"x-course-app-signature","in":"header","required":false,"description":"HMAC-SHA256 of the raw body with the org’s course-app secret.","schema":{"type":"string"}}],"responses":{"201":{"description":"{ enrollmentId, status, requirementId, evidenceRecorded } or { duplicate: true }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"orgid header is required — No `orgid` header.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"orgid header is required","path":"/business-made/training/marketplace/completion","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"description":"Completion signature is missing or invalid — The webhook signature does not verify with the course app secret.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":401,"error":"Completion signature is missing or invalid","path":"/business-made/training/marketplace/completion","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"No enrollment matches this completion — The enrollment id matches nothing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"No enrollment matches this completion","path":"/business-made/training/marketplace/completion","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"The enrollment belongs to someone else — The enrollment and the learner disagree.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"The enrollment belongs to someone else","path":"/business-made/training/marketplace/completion","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Course completion from the course app","description":"Webhook-style. Accepted when signed by the course app (HMAC-SHA256 of the raw body with the integration’s webhookSecret), or posted by an owner/admin. The course is identified by `enrollmentId` or `courseId` (both echoed from the launch link) or the app’s `marketplaceCourseId`; the person by the enrollment, `employeeId` or `email`. Idempotent on `reference`, including references HR has undone. Records `externalCompletion` and a completionHistory entry on the enrollment (creating it if needed), sets expiry from the requirement’s renewal else the course’s, records readiness evidence when passed and emits `journeys.evidence`.\n\n#### Signature\n\n```http\nPOST /business-made/training/marketplace/completion (body) -> { enrollmentId, status, requirementId, evidenceRecorded } or { duplicate: true }\n```\n\n#### Access\n\nPublic — no credentials required.\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | ORG_REQUIRED | orgid header is required | No `orgid` header. | — |\n| `401` | WEBHOOK_SIGNATURE_INVALID | Completion signature is missing or invalid | The webhook signature does not verify with the course app secret. | — |\n| `404` | ENROLLMENT_NOT_FOUND | No enrollment matches this completion | The enrollment id matches nothing. | — |\n| `409` | ENROLLMENT_MISMATCH | The enrollment belongs to someone else | The enrollment and the learner disagree. | — |\n\nPlus the standard platform errors: `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string"},"marketplaceCourseId":{"type":"string"},"courseId":{"type":"string"},"enrollmentId":{"type":"string"},"reference":{"type":"string"},"employeeId":{"type":"string"},"email":{"type":"string"},"completedAt":{"type":"string"},"score":{"type":"number"},"passed":{"type":"boolean"},"certificateUrl":{"type":"string"},"courseVersion":{"type":"string"}}}}}}}},"/business-made/signoffs/checklist":{"get":{"operationId":"TrainingController_checklist","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"requirementId","required":true,"in":"query","schema":{"type":"string"}},{"name":"employee","required":false,"in":"query","schema":{"type":"string"},"description":"Employee code or sk (optional)."}],"responses":{"200":{"description":"{ requirementId, title, items: [{ key, label, required, photoRequired }], stations, observerRoles, prerequisiteRuleIds, renewalMonths, canObserve, cannotObserveReason?, employee? }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"requirementId is required — `requirementId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"requirementId is required","path":"/business-made/signoffs/checklist","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Requirement not found — No rule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Requirement not found","path":"/business-made/signoffs/checklist","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Sign-off checklist","description":"The checklist for a sign-off rule, whether the caller may observe, and — with `employee` — that person’s prerequisites and last sign-off.\n\n#### Signature\n\n```http\nGET /business-made/signoffs/checklist (requirementId?: string, employee?: string) -> { requirementId, title, items: [{ key, label, required, photoRequired }], stations, observerRoles, prerequisiteRuleIds, renewalMonths, canObserve, cannotObserveReason?, employee? }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | REQUIREMENT_ID_REQUIRED | requirementId is required | `requirementId` is missing. | — |\n| `404` | REQUIREMENT_NOT_FOUND | Requirement not found | No rule has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/signoffs":{"get":{"operationId":"TrainingController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employee","required":false,"in":"query","schema":{"type":"string"}},{"name":"station","required":false,"in":"query","schema":{"type":"string"}},{"name":"requirementId","required":false,"in":"query","schema":{"type":"string"}},{"name":"result","required":false,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"type":"integer"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"{ data, total, page, pageSize } (observer signature image omitted)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"List sign-offs","description":"#### Signature\n\n```http\nGET /business-made/signoffs (employee?: string, station?: string, requirementId?: string, result?: string, page?: integer, pageSize?: integer) -> { data, total, page, pageSize } (observer signature image omitted)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"post":{"operationId":"TrainingController_create","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The bm_signoff","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"requirementId is required — `requirementId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"requirementId is required","path":"/business-made/signoffs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only … may sign this off — Caller’s roles are not in the rule’s observerRoles.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only … may sign this off","path":"/business-made/signoffs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Requirement not found — No rule has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Requirement not found","path":"/business-made/signoffs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Prerequisite training is not complete — A pass before the prerequisite rules are met.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Prerequisite training is not complete","path":"/business-made/signoffs","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Training"],"summary":"Record a floor sign-off","description":"The observer is the caller and must hold one of the rule’s observerRoles (or be the person’s manager when none are set; owners/admins always may; never yourself). Every checklist line needs pass/fail; photoRequired lines need photoIds; a pass needs all required lines passed and prerequisites complete. A pass expires after the rule’s renewalMonths and is readiness evidence.\n\n#### Signature\n\n```http\nPOST /business-made/signoffs (body) -> The bm_signoff\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | NOT_AN_OBSERVER | Only … may sign this off | Caller’s roles are not in the rule’s observerRoles. | — |\n| `409` | PREREQUISITES_NOT_MET | Prerequisite training is not complete | A pass before the prerequisite rules are met. | — |\n| `400` | REQUIREMENT_ID_REQUIRED | requirementId is required | `requirementId` is missing. | — |\n| `404` | REQUIREMENT_NOT_FOUND | Requirement not found | No rule has that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"employeeId":{"type":"string"},"requirementId":{"type":"string"},"station":{"type":"string"},"result":{"type":"string","enum":["pass","fail","needs_practice"]},"checklist":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"passed":{"type":"boolean"},"note":{"type":"string"},"photoIds":{"type":"array","items":{"type":"string"}}}}},"photos":{"type":"array","items":{"type":"object","additionalProperties":true}},"notes":{"type":"string"},"observerSignature":{"type":"object","properties":{"typedName":{"type":"string"},"image":{"type":"string","description":"PNG/JPEG/SVG data URL"},"consent":{"type":"boolean","description":"Must be true"}}}}}}}}}},"/staff-portal/forms":{"get":{"operationId":"StaffFormsController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ data: FormSummary[] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Forms"],"summary":"All my government forms","description":"One summary per form key — title, jurisdiction, status, `applies` (false = not required for me; hide it), `fillable` (a schema exists), signedAt, expiresOn, expiredAt, renewalRule, optional, message, source. The staff Tax forms screen lists these and opens each with GET /staff-portal/forms/{formKey}.\n\n#### Signature\n\n```http\nGET /staff-portal/forms () -> { data: FormSummary[] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/staff-portal/forms/{formKey}":{"get":{"operationId":"StaffFormsController_get","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"formKey","required":true,"in":"path","schema":{"type":"string","enum":["w4","state-withholding","i9-section1","w9","contractor-agreement"]}},{"name":"variant","required":false,"in":"query","schema":{"type":"string"},"description":"state-withholding only: schemaVersion of an alternative certificate listed in `alternatives`."}],"responses":{"200":{"description":"FormView","content":{"application/json":{"schema":{"type":"object","properties":{"formKey":{"type":"string"},"jurisdiction":{"type":"string","nullable":true},"schemaVersion":{"type":"string","nullable":true},"data":{"type":"object","additionalProperties":true},"status":{"type":"string","enum":["not_started","draft","signed","pending_countersign","expired","not_required","federal_w4_used","not_available"]},"signedAt":{"type":"string"},"title":{"type":"string"},"attestation":{"type":"string"},"schema":{"type":"object","additionalProperties":true,"description":"JSON Schema the UI renders; enums carry labels (enumNames / x-enum)"},"message":{"type":"string","description":"Why a form is not required / not available / replaced by the federal W-4, or that a state certificate is optional"},"source":{"type":"string","description":"Official form URL"},"expiresOn":{"type":"string","description":"YYYY-MM-DD the signed form is good through (exempt claims, org renewal period)"},"expiredAt":{"type":"string"},"renewalRule":{"type":"string","description":"Plain-language annual exempt-renewal rule of this form"},"optional":{"type":"boolean","description":"The state accepts the federal W-4 instead of this certificate"},"alternatives":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Other certificates this state accepts: [{ variant, title, officialForm, current }]"},"signatures":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Unknown form \"w5\" — `formKey` is not a known form; the body lists `allowed`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown form \"w5\"","path":"/staff-portal/forms/{formKey}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Forms"],"summary":"My government form","description":"W-4 (2026); the work state’s withholding certificate — every state that taxes wages plus DC (see `STATE_WITHHOLDING_FORMS` in gov-forms/form-definitions.ts, each citing its official PDF). States with no wage income tax answer `not_required`; states that compute withholding from the federal W-4 and have no certificate of their own answer `federal_w4_used` with a message; a state whose own certificate is optional returns it with `optional: true`. Where a state accepts more than one certificate (NC-4 / NC-4EZ; NY IT-2104 / IT-2104-E for exemption) the view lists `alternatives: [{ variant, title, officialForm, current }]`; re-open with `?variant=<schemaVersion>` and send `variant` in the PUT body to fill the other one. A certificate signed for a previous work state is not shown for the new state. Also I-9 Section 1 (edition 01/20/25), and for contractors W-9 (Rev. March 2024) and the contractor agreement. Pre-filled from the employee record; SSN/TIN and document numbers are masked.\n\n**Expiry.** A W-4 claiming exemption is good through February 15 of the year after it is signed (IRS Pub 15); state certificates whose exempt claim must be renewed each year carry their own date (e.g. MN W-4MN, NE W-4N, MD MW507 line 3: Feb 15). W-9 and the contractor agreement expire only if the org sets a renewal period (`PUT /business-made/gov-forms/renewal-settings`). The date is stamped on the document (`expirationDate`) when the employee signs, so readiness shows the requirement due soon ahead of it. The day after, the form becomes `expired`: readiness re-reads the evidence (the requirement goes due / overdue), `journeys.evidence` is emitted, and payroll readiness exceptions list it. An expired exempt W-4 switches the payroll profile to the IRS default — Single / MFS with no Step 2–4 entries (Pub 15 §9; an older non-exempt W-4 is not revived) — and says so. An expired state exemption turns `stateTax.exempt` off. Expiry runs on read (this endpoint, the exceptions list) and daily via `GovFormsService.expireDueForms(orgId)`.\n\n#### Signature\n\n```http\nGET /staff-portal/forms/{formKey} (formKey: string, variant?: string) -> FormView\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | UNKNOWN_FORM | Unknown form \"w5\" | `formKey` is not a known form; the body lists `allowed`. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"StaffFormsController_put","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"formKey","required":true,"in":"path","schema":{"type":"string","enum":["w4","state-withholding","i9-section1","w9","contractor-agreement"]}}],"responses":{"200":{"description":"FormView","content":{"application/json":{"schema":{"type":"object","properties":{"formKey":{"type":"string"},"jurisdiction":{"type":"string","nullable":true},"schemaVersion":{"type":"string","nullable":true},"data":{"type":"object","additionalProperties":true},"status":{"type":"string","enum":["not_started","draft","signed","pending_countersign","expired","not_required","federal_w4_used","not_available"]},"signedAt":{"type":"string"},"title":{"type":"string"},"attestation":{"type":"string"},"schema":{"type":"object","additionalProperties":true,"description":"JSON Schema the UI renders; enums carry labels (enumNames / x-enum)"},"message":{"type":"string","description":"Why a form is not required / not available / replaced by the federal W-4, or that a state certificate is optional"},"source":{"type":"string","description":"Official form URL"},"expiresOn":{"type":"string","description":"YYYY-MM-DD the signed form is good through (exempt claims, org renewal period)"},"expiredAt":{"type":"string"},"renewalRule":{"type":"string","description":"Plain-language annual exempt-renewal rule of this form"},"optional":{"type":"boolean","description":"The state accepts the federal W-4 instead of this certificate"},"alternatives":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Other certificates this state accepts: [{ variant, title, officialForm, current }]"},"signatures":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"The form has problems — Validation failed; `errors` lists each problem.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The form has problems","path":"/staff-portal/forms/{formKey}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"I-9 Section 1 is signed — Editing after signature.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"I-9 Section 1 is signed","path":"/staff-portal/forms/{formKey}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"Not required — e.g. state withholding in TX/FL, W-4 for a contractor.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"Not required","path":"/staff-portal/forms/{formKey}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Staff Portal · Forms"],"summary":"Save or sign my form","description":"Saves answers (a masked value sent back means unchanged). With `signature` the form is validated for signing and signed; the server stamps signedAt, IP and user agent. A signed W-4 / state certificate updates the payroll profile’s tax elections, and every signed form is readiness evidence. Re-saving a signed tax form opens a new version; the signed one stays in force until the new one is signed. A signed I-9 Section 1 is locked.\n\n#### Signature\n\n```http\nPUT /staff-portal/forms/{formKey} (formKey: string, body) -> FormView\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | FORM_INVALID | The form has problems | Validation failed; `errors` lists each problem. | — |\n| `409` | I9_SECTION1_LOCKED | I-9 Section 1 is signed | Editing after signature. | — |\n| `422` | FORM_NOT_REQUIRED | Not required | e.g. state withholding in TX/FL, W-4 for a contractor. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true},"signature":{"type":"object","properties":{"typedName":{"type":"string"},"image":{"type":"string","description":"PNG/JPEG/SVG data URL"},"consent":{"type":"boolean"}}}}},"example":{"data":{"filingStatus":"single"},"signature":{"typedName":"Ana Ruiz","consent":true}}}}}}},"/business-made/gov-forms/renewal-settings":{"get":{"operationId":"GovFormsController_renewalSettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ renewalMonths: { w9?: number, \"contractor-agreement\"?: number } }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/gov-forms/renewal-settings","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Form renewal periods (org setting)","description":"Stored as the `gov_forms_renewal` setting. `renewalMonths` per form: only `w9` and `contractor-agreement` (tax-form expiry is set by law). Absent = never expires.\n\n#### Signature\n\n```http\nGET /business-made/gov-forms/renewal-settings () -> { renewalMonths: { w9?: number, \"contractor-agreement\"?: number } }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."},"put":{"operationId":"GovFormsController_setRenewalSettings","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"{ renewalMonths, restamped }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The renewal settings have problems — Unknown form or months out of range; `errors` lists each.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The renewal settings have problems","path":"/business-made/gov-forms/renewal-settings","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/gov-forms/renewal-settings","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Set form renewal periods","description":"Whole months 1–120, or null to remove. Signed forms of that kind are re-dated at once (good through the day before the anniversary of signing), so readiness moves them to due soon / expired on the new schedule.\n\n#### Signature\n\n```http\nPUT /business-made/gov-forms/renewal-settings (body) -> { renewalMonths, restamped }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | RENEWAL_SETTINGS_INVALID | The renewal settings have problems | Unknown form or months out of range; `errors` lists each. | — |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"renewalMonths":{"type":"object","additionalProperties":true}}},"example":{"renewalMonths":{"w9":12,"contractor-agreement":24}}}}}}},"/business-made/gov-forms/expire-due":{"post":{"operationId":"GovFormsController_expireDue","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"asOf","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":"{ asOf, checked, expired: [{ employeeId, documentId, formKey, jurisdiction, expiresOn, basis: exempt_claim|org_renewal, payrollEffect? }] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"asOf must be a date (YYYY-MM-DD) — `asOf` is not YYYY-MM-DD.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"asOf must be a date (YYYY-MM-DD)","path":"/business-made/gov-forms/expire-due","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/gov-forms/expire-due","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Expire forms past their date now","description":"Runs the same pass as the daily readiness run (`GovFormsService.expireDueForms`). Idempotent.\n\n**Expiry.** A W-4 claiming exemption is good through February 15 of the year after it is signed (IRS Pub 15); state certificates whose exempt claim must be renewed each year carry their own date (e.g. MN W-4MN, NE W-4N, MD MW507 line 3: Feb 15). W-9 and the contractor agreement expire only if the org sets a renewal period (`PUT /business-made/gov-forms/renewal-settings`). The date is stamped on the document (`expirationDate`) when the employee signs, so readiness shows the requirement due soon ahead of it. The day after, the form becomes `expired`: readiness re-reads the evidence (the requirement goes due / overdue), `journeys.evidence` is emitted, and payroll readiness exceptions list it. An expired exempt W-4 switches the payroll profile to the IRS default — Single / MFS with no Step 2–4 entries (Pub 15 §9; an older non-exempt W-4 is not revived) — and says so. An expired state exemption turns `stateTax.exempt` off. Expiry runs on read (this endpoint, the exceptions list) and daily via `GovFormsService.expireDueForms(orgId)`.\n\n#### Signature\n\n```http\nPOST /business-made/gov-forms/expire-due () -> { asOf, checked, expired: [{ employeeId, documentId, formKey, jurisdiction, expiresOn, basis: exempt_claim\\|org_renewal, payrollEffect? }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `400` | INVALID_AS_OF | asOf must be a date (YYYY-MM-DD) | `asOf` is not YYYY-MM-DD. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/business-made/gov-forms/{employeeId}/{formKey}":{"get":{"operationId":"GovFormsController_getForm","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"formKey","required":true,"in":"path","schema":{"type":"string","enum":["w4","state-withholding","i9-section1","w9","contractor-agreement"]}},{"name":"variant","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"FormView","content":{"application/json":{"schema":{"type":"object","properties":{"formKey":{"type":"string"},"jurisdiction":{"type":"string","nullable":true},"schemaVersion":{"type":"string","nullable":true},"data":{"type":"object","additionalProperties":true},"status":{"type":"string","enum":["not_started","draft","signed","pending_countersign","expired","not_required","federal_w4_used","not_available"]},"signedAt":{"type":"string"},"title":{"type":"string"},"attestation":{"type":"string"},"schema":{"type":"object","additionalProperties":true,"description":"JSON Schema the UI renders; enums carry labels (enumNames / x-enum)"},"message":{"type":"string","description":"Why a form is not required / not available / replaced by the federal W-4, or that a state certificate is optional"},"source":{"type":"string","description":"Official form URL"},"expiresOn":{"type":"string","description":"YYYY-MM-DD the signed form is good through (exempt claims, org renewal period)"},"expiredAt":{"type":"string"},"renewalRule":{"type":"string","description":"Plain-language annual exempt-renewal rule of this form"},"optional":{"type":"boolean","description":"The state accepts the federal W-4 instead of this certificate"},"alternatives":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Other certificates this state accepts: [{ variant, title, officialForm, current }]"},"signatures":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Unknown form \"w5\" — `formKey` is not a known form; the body lists `allowed`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Unknown form \"w5\"","path":"/business-made/gov-forms/{employeeId}/{formKey}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/gov-forms/{employeeId}/{formKey}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"An employee’s form (HR view)","description":"#### Signature\n\n```http\nGET /business-made/gov-forms/{employeeId}/{formKey} (employeeId: string, formKey: string) -> FormView\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `400` | UNKNOWN_FORM | Unknown form \"w5\" | `formKey` is not a known form; the body lists `allowed`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/business-made/gov-forms/{employeeId}/{formKey}/countersign":{"post":{"operationId":"GovFormsController_countersign","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"formKey","required":true,"in":"path","schema":{"type":"string","enum":["w4","state-withholding","i9-section1","w9","contractor-agreement"]}}],"responses":{"201":{"description":"FormView","content":{"application/json":{"schema":{"type":"object","properties":{"formKey":{"type":"string"},"jurisdiction":{"type":"string","nullable":true},"schemaVersion":{"type":"string","nullable":true},"data":{"type":"object","additionalProperties":true},"status":{"type":"string","enum":["not_started","draft","signed","pending_countersign","expired","not_required","federal_w4_used","not_available"]},"signedAt":{"type":"string"},"title":{"type":"string"},"attestation":{"type":"string"},"schema":{"type":"object","additionalProperties":true,"description":"JSON Schema the UI renders; enums carry labels (enumNames / x-enum)"},"message":{"type":"string","description":"Why a form is not required / not available / replaced by the federal W-4, or that a state certificate is optional"},"source":{"type":"string","description":"Official form URL"},"expiresOn":{"type":"string","description":"YYYY-MM-DD the signed form is good through (exempt claims, org renewal period)"},"expiredAt":{"type":"string"},"renewalRule":{"type":"string","description":"Plain-language annual exempt-renewal rule of this form"},"optional":{"type":"boolean","description":"The state accepts the federal W-4 instead of this certificate"},"alternatives":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Other certificates this state accepts: [{ variant, title, officialForm, current }]"},"signatures":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"The signature is incomplete — A signature was sent without consent, or with neither a typed name nor an image.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The signature is incomplete","path":"/business-made/gov-forms/{employeeId}/{formKey}/countersign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/gov-forms/{employeeId}/{formKey}/countersign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"The contractor has not signed yet, or it is already countersigned — The agreement is not pending a countersignature.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"The contractor has not signed yet, or it is already countersigned","path":"/business-made/gov-forms/{employeeId}/{formKey}/countersign","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Countersign a contractor agreement","description":"#### Signature\n\n```http\nPOST /business-made/gov-forms/{employeeId}/{formKey}/countersign (employeeId: string, formKey: string, body) -> FormView\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `400` | SIGNATURE_INVALID | The signature is incomplete | A signature was sent without consent, or with neither a typed name nor an image. | — |\n| `409` | NOT_AWAITING_COUNTERSIGN | The contractor has not signed yet, or it is already countersigned | The agreement is not pending a countersignature. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"signature":{"type":"object","properties":{"typedName":{"type":"string"},"image":{"type":"string","description":"PNG/JPEG/SVG data URL"},"consent":{"type":"boolean"}}}}}}}}}},"/business-made/i9/due":{"get":{"operationId":"GovFormsController_due","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"withinDays","required":false,"in":"query","schema":{"type":"integer","default":90}},{"name":"asOf","required":false,"in":"query","schema":{"type":"string"},"description":"YYYY-MM-DD, default today"},{"name":"location","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"{ asOf, withinDays, data: [{ employeeId, name, startDate, code, section1Status, section2Status, section2DueDate, daysLate, reverifyBy, daysToReverify }], counts, total }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/i9/due","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"I-9s needing attention","description":"Section 1 missing on/after day 1, Section 2 due or late (3 business days after the first day, federal holidays excluded), and reverification due within `withinDays` (default 90) or overdue.\n\n#### Signature\n\n```http\nGET /business-made/i9/due (withinDays?: integer, asOf?: string, location?: string) -> { asOf, withinDays, data: [{ employeeId, name, startDate, code, section1Status, section2Status, section2DueDate, daysLate, reverifyBy, daysToReverify }], counts, total }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/business-made/i9/{employeeId}/section2":{"get":{"operationId":"GovFormsController_getSection2","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"}],"responses":{"200":{"description":"{ employeeId, name, startDate, section1: { status, signedAt, data }, section2: { status, data, dueDate, late, daysLate, signedAt }, reverifyBy, supplementB, schema, attestation }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/i9/{employeeId}/section2","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"I-9 Section 2","description":"#### Signature\n\n```http\nGET /business-made/i9/{employeeId}/section2 (employeeId: string) -> { employeeId, name, startDate, section1: { status, signedAt, data }, section2: { status, data, dueDate, late, daysLate, signedAt }, reverifyBy, supplementB, schema, attestation }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."},"put":{"operationId":"GovFormsController_putSection2","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"}],"responses":{"200":{"description":"As GET","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Send { data } to save, and { signature: { typedName|image, consent: true } } to sign — The body has neither `data` nor a signature.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Send { data } to save, and { signature: { typedName|image, consent: true } } to sign","path":"/business-made/i9/{employeeId}/section2","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"You cannot complete the employer section of your own I-9 — Caller is the employee.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"You cannot complete the employer section of your own I-9","path":"/business-made/i9/{employeeId}/section2","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Section 1 must be signed first — Signing Section 2 before Section 1.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Section 1 must be signed first","path":"/business-made/i9/{employeeId}/section2","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Save or sign I-9 Section 2","description":"List A, or List B + List C, with issuing authority, number and expiry; first day of employment; employer representative. Signing needs Section 1 signed and sets the reverification date for time-limited work authorisation. The representative cannot complete their own I-9.\n\n#### Signature\n\n```http\nPUT /business-made/i9/{employeeId}/section2 (employeeId: string, body) -> As GET\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | I9_SECTION1_UNSIGNED | Section 1 must be signed first | Signing Section 2 before Section 1. | — |\n| `403` | I9_SELF_REVIEW | You cannot complete the employer section of your own I-9 | Caller is the employee. | — |\n| `400` | FORM_BODY_REQUIRED | Send { data } to save, and { signature: { typedName\\|image, consent: true } } to sign | The body has neither `data` nor a signature. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true},"signature":{"type":"object","properties":{"typedName":{"type":"string"},"image":{"type":"string","description":"PNG/JPEG/SVG data URL"},"consent":{"type":"boolean"}}}}},"example":{"data":{"filingStatus":"single"},"signature":{"typedName":"Ana Ruiz","consent":true}}}}}}},"/business-made/i9/{employeeId}/reverification":{"post":{"operationId":"GovFormsController_reverify","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"}],"responses":{"201":{"description":"As GET section2","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/i9/{employeeId}/reverification","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Reverify (Supplement B)","description":"#### Signature\n\n```http\nPOST /business-made/i9/{employeeId}/reverification (employeeId: string, body) -> As GET section2\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document":{"type":"object","additionalProperties":true},"newName":{"type":"object","additionalProperties":true},"rehireDate":{"type":"string"},"signature":{"type":"object","properties":{"typedName":{"type":"string"},"image":{"type":"string","description":"PNG/JPEG/SVG data URL"},"consent":{"type":"boolean"}}}}}}}}}},"/business-made/everify/{employeeId}/case":{"post":{"operationId":"GovFormsController_createCase","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"}],"responses":{"201":{"description":"The case (payload masked): { caseId, status, caseNumber, eligibility, closureReason, missing, payload (masked), transmission: { connected, message, detail, environment, remote, attempts, lastError, submittedAt, lastCheckedAt, nextCheckAt, polls }, tnc, photo, history: [{ at, status, by, source, note, caseNumber, raw }] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}/case","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Both I-9 sections must be signed — I-9 incomplete.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Both I-9 sections must be signed","path":"/business-made/everify/{employeeId}/case","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Create an E-Verify case","description":"Builds the E-Verify create-case payload from the signed I-9. The employer company ID comes from the org's E-Verify connection (body `clientCompanyId` overrides). A complete payload is `queued` and, when the org is connected, created and submitted in E-Verify at once; the response carries the result. Without a connection the case stays `queued` with `transmission.message: \"E-Verify not connected\"` — not an error — and is sent automatically when the connection is saved. An incomplete payload is `draft` with `missing`.\n\nSigning I-9 Section 2 does this automatically for an org that has an E-Verify connection.\n\n**Status refresh:** after submission a delayed `everify-refresh` job on `schedule-queue` checks the case (2 h while E-Verify is verifying, daily while a TNC, referral, continuance or employer action is open) until it closes, up to 90 checks. A temporary failure (5xx, 429, network) is retried with backoff; a rejected case (4xx) is not.\n\n**Evidence:** `queued` and `employment_authorized` record readiness evidence; every status change emits `journeys.evidence`.\n\nEmployment Authorized cases are closed with `EMPLOYMENT_AUTHORIZED` automatically unless the connection sets `autoCloseAuthorized: false`.\n\n#### Signature\n\n```http\nPOST /business-made/everify/{employeeId}/case (employeeId: string, body) -> The case (payload masked): { caseId, status, caseNumber, eligibility, closureReason, missing, payload (masked), transmission: { connected, message, detail, environment, remote, attempts, lastError, submittedAt, lastCheckedAt, nextCheckAt, polls }, tnc, photo, history: [{ at, status, by, source, note, caseNumber, raw }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | I9_INCOMPLETE | Both I-9 sections must be signed | I-9 incomplete. | — |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clientCompanyId":{"type":"string"},"caseCreatorPhone":{"type":"string"},"reasonForDelayCode":{"type":"string","enum":["AWAITING_SSN","TECHNICAL_PROBLEMS","AUDIT_REVELATION","FEDERAL_CONTRACTOR_WITH_EVERIFY_CLAUSE","OTHER"]},"reasonForDelayDescription":{"type":"string"}}}}}}}},"/business-made/everify/{employeeId}":{"get":{"operationId":"GovFormsController_getCases","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"}],"responses":{"200":{"description":"{ employeeId, name, startDate, deadline, late, i9, canCreate, missingFromI9, connection: { connected, message?, detail?, environment?, companyId? }, latest, cases, statuses, closureReasons }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"E-Verify cases for an employee","description":"#### Signature\n\n```http\nGET /business-made/everify/{employeeId} (employeeId: string) -> { employeeId, name, startDate, deadline, late, i9, canCreate, missingFromI9, connection: { connected, message?, detail?, environment?, companyId? }, latest, cases, statuses, closureReasons }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/business-made/everify/{employeeId}/case/{caseId}/status":{"post":{"operationId":"GovFormsController_updateCase","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"caseId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"The case","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"status must be one of … — `status` is not an E-Verify case status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"status must be one of …","path":"/business-made/everify/{employeeId}/case/{caseId}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}/case/{caseId}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"E-Verify case not found — The person has no case with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-Verify case not found","path":"/business-made/everify/{employeeId}/case/{caseId}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Case is closed — The case is in a terminal status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Case is closed","path":"/business-made/everify/{employeeId}/case/{caseId}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"The payload is incomplete — Queuing a case whose payload misses fields; the body lists `missing`.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"The payload is incomplete","path":"/business-made/everify/{employeeId}/case/{caseId}/status","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Record an E-Verify result by hand","description":"For cases worked in the E-Verify web portal: record a status (and case number / closure reason), or `{ rebuild: true }` a draft from the current I-9 (the company ID comes from the connection). A case that becomes `queued` is transmitted when connected.\n\n#### Signature\n\n```http\nPOST /business-made/everify/{employeeId}/case/{caseId}/status (employeeId: string, caseId: string, body) -> The case\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |\n| `409` | EVERIFY_CASE_CLOSED | Case is closed | The case is in a terminal status. | — |\n| `400` | EVERIFY_STATUS_INVALID | status must be one of … | `status` is not an E-Verify case status. | — |\n| `422` | EVERIFY_PAYLOAD_INCOMPLETE | The payload is incomplete | Queuing a case whose payload misses fields; the body lists `missing`. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"caseNumber":{"type":"string"},"closureReason":{"type":"string"},"note":{"type":"string"},"rebuild":{"type":"boolean"}}}}}}}},"/business-made/everify/{employeeId}/case/{caseId}/submit":{"post":{"operationId":"GovFormsController_submitCase","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"caseId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"The case","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}/case/{caseId}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"E-Verify case not found — The person has no case with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-Verify case not found","path":"/business-made/everify/{employeeId}/case/{caseId}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Only queued cases are transmitted — Case is draft or already sent.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Only queued cases are transmitted","path":"/business-made/everify/{employeeId}/case/{caseId}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"E-Verify POST /cases failed (422): … — E-Verify rejected or could not take the case; `errors` carries E-Verify's list. Recorded on the case.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"E-Verify POST /cases failed (422): …","path":"/business-made/everify/{employeeId}/case/{caseId}/submit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Business Made · Government forms"],"summary":"Submit a queued case to E-Verify now","description":"Creates the case in E-Verify (once — the case number is kept, so a retry only re-submits) and submits it. Not connected: returns the case, still `queued`, with `transmission.message: \"E-Verify not connected\"` and `transmission.detail` naming what is missing.\n\n#### Signature\n\n```http\nPOST /business-made/everify/{employeeId}/case/{caseId}/submit (employeeId: string, caseId: string) -> The case\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | EVERIFY_NOT_QUEUED | Only queued cases are transmitted | Case is draft or already sent. | — |\n| `502` | EVERIFY_UPSTREAM_ERROR | E-Verify POST /cases failed (422): … | E-Verify rejected or could not take the case; `errors` carries E-Verify's list. Recorded on the case. | — |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/business-made/everify/{employeeId}/case/{caseId}/refresh":{"post":{"operationId":"GovFormsController_refreshCase","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"caseId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"The case","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}/case/{caseId}/refresh","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"E-Verify case not found — The person has no case with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-Verify case not found","path":"/business-made/everify/{employeeId}/case/{caseId}/refresh","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"E-Verify GET … failed — E-Verify unavailable.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"E-Verify GET … failed","path":"/business-made/everify/{employeeId}/case/{caseId}/refresh","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Business Made · Government forms"],"summary":"Refresh a case from E-Verify","description":"Reads the case from E-Verify and records any change in the history (and auto-closes Employment Authorized). Cases not sent through the connection are returned unchanged.\n\n#### Signature\n\n```http\nPOST /business-made/everify/{employeeId}/case/{caseId}/refresh (employeeId: string, caseId: string) -> The case\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `502` | EVERIFY_UPSTREAM_ERROR | E-Verify GET … failed | E-Verify unavailable. | — |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`."}},"/business-made/everify/{employeeId}/case/{caseId}/tnc":{"post":{"operationId":"GovFormsController_tnc","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"caseId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"The case","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"action must be acknowledge or refer — `action` is anything else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"action must be acknowledge or refer","path":"/business-made/everify/{employeeId}/case/{caseId}/tnc","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}/case/{caseId}/tnc","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"E-Verify case not found — The person has no case with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-Verify case not found","path":"/business-made/everify/{employeeId}/case/{caseId}/tnc","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"The case is …, not a Tentative Nonconfirmation — Wrong status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"The case is …, not a Tentative Nonconfirmation","path":"/business-made/everify/{employeeId}/case/{caseId}/tnc","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Tentative Nonconfirmation: acknowledge or refer","description":"`acknowledge` confirms the employee was given the Further Action Notice (`notifiedDate`, optional `language`). `refer` records that the employee contests it; the case moves to `referred` and is checked daily. Acknowledge comes first. Portal-worked cases are recorded here only.\n\n#### Signature\n\n```http\nPOST /business-made/everify/{employeeId}/case/{caseId}/tnc (employeeId: string, caseId: string, body) -> The case\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | EVERIFY_NOT_TNC | The case is …, not a Tentative Nonconfirmation | Wrong status. | — |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |\n| `400` | EVERIFY_TNC_ACTION_INVALID | action must be acknowledge or refer | `action` is anything else. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["acknowledge","refer"]},"notifiedDate":{"type":"string"},"language":{"type":"string"},"note":{"type":"string"}}},"example":{"action":"acknowledge","notifiedDate":"2026-09-26"}}}}}},"/business-made/everify/{employeeId}/case/{caseId}/photo-match":{"post":{"operationId":"GovFormsController_photoMatch","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"caseId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"The case","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"match must be one of match, no_match, no_photo — `match` is anything else.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"match must be one of match, no_match, no_photo","path":"/business-made/everify/{employeeId}/case/{caseId}/photo-match","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}/case/{caseId}/photo-match","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"E-Verify case not found — The person has no case with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-Verify case not found","path":"/business-made/everify/{employeeId}/case/{caseId}/photo-match","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This case was not transmitted through the E-Verify connection — Portal-worked case.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This case was not transmitted through the E-Verify connection","path":"/business-made/everify/{employeeId}/case/{caseId}/photo-match","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Answer photo matching","description":"For `photo_matching_required`: does the photo E-Verify shows match the employee's document? `match`, `no_match` or `no_photo`. Transmitted cases only.\n\n#### Signature\n\n```http\nPOST /business-made/everify/{employeeId}/case/{caseId}/photo-match (employeeId: string, caseId: string, body) -> The case\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | EVERIFY_NOT_TRANSMITTED | This case was not transmitted through the E-Verify connection | Portal-worked case. | — |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |\n| `400` | EVERIFY_PHOTO_MATCH_INVALID | match must be one of match, no_match, no_photo | `match` is anything else. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"match":{"type":"string","enum":["match","no_match","no_photo"]}}}}}}}},"/business-made/everify/{employeeId}/case/{caseId}/close":{"post":{"operationId":"GovFormsController_closeCase","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code (badge number) or bm_employee sk.","example":"E004"},{"name":"caseId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"The case","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"closureReason must be one of … — Missing or unknown reason.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"closureReason must be one of …","path":"/business-made/everify/{employeeId}/case/{caseId}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. — The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal.","path":"/business-made/everify/{employeeId}/case/{caseId}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"E-Verify case not found — The person has no case with that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-Verify case not found","path":"/business-made/everify/{employeeId}/case/{caseId}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Case is closed — The case is already in a terminal status.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Case is closed","path":"/business-made/everify/{employeeId}/case/{caseId}/close","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Close an E-Verify case","description":"Closes with an E-Verify closure statement (`closureReason`, one of `closureReasons` from GET; `OTHER` needs `description`). `currentlyEmployed` is sent where E-Verify asks. A case never sent to E-Verify is `cancelled`. Pending status checks are cancelled.\n\n#### Signature\n\n```http\nPOST /business-made/everify/{employeeId}/case/{caseId}/close (employeeId: string, caseId: string, body) -> The case\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EVERIFY_CLOSURE_REASON_REQUIRED | closureReason must be one of … | Missing or unknown reason. | — |\n| `403` | GOV_FORMS_HR_ONLY | Only HR or an admin can see other people's government forms. Your own are under Tax forms in the staff portal. | The caller has no admin role. Applies to every /business-made gov-forms, I-9 and E-Verify route. | — |\n| `404` | EVERIFY_CASE_NOT_FOUND | E-Verify case not found | The person has no case with that id. | — |\n| `409` | EVERIFY_CASE_CLOSED | Case is closed | The case is already in a terminal status. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"closureReason":{"type":"string"},"currentlyEmployed":{"type":"boolean"},"description":{"type":"string"},"note":{"type":"string"}}},"example":{"closureReason":"EMPLOYEE_QUIT","currentlyEmployed":false}}}}}},"/business-made/payroll/readiness-exceptions":{"get":{"operationId":"GovFormsController_readinessExceptions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"payDate","required":false,"in":"query","schema":{"type":"string"},"description":"YYYY-MM-DD, default today"},{"name":"location","required":false,"in":"query","schema":{"type":"string"}},{"name":"employeeIds","required":false,"in":"query","schema":{"type":"string"},"description":"Comma-separated codes"}],"responses":{"200":{"description":"{ payDate, data: [{ employeeId, name, code, message, fix: { route, label }, effect, requirementId? }], counts }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government forms"],"summary":"Pre-payroll exceptions","description":"Everyone with a problem for this pay date, each with a one-tap fix route: missing_w4 (warn — Single, no adjustments applies), w4_exempt_expired (warn — the IRS default withholding has been applied and the message says so), form_expired (warn — state certificate, W-9 or contractor agreement past its date), form_expiring (warn — good through a date within 30 days), i9_section2_late (warn), reverification_due within 90 days (warn), no_pay_method (block — held with notice), unsigned_policy (per the readiness payroll gate). A payroll-gate rule can raise an effect to block, or override while an override is active.\n\n#### Signature\n\n```http\nGET /business-made/payroll/readiness-exceptions (payDate?: string, location?: string, employeeIds?: string) -> { payDate, data: [{ employeeId, name, code, message, fix: { route, label }, effect, requirementId? }], counts }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/journeys/accounts/{employeeId}":{"get":{"operationId":"JourneyAccountsController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code or bm_employee sk.","example":"EMP-1043"}],"responses":{"200":{"description":"`{ data:[{key, provider, app, label, email, externalId, status, groups, updatedAt, note, by, automated}], employeeId, name }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR, an admin or the person’s manager can see their accounts — The caller is none of those.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR, an admin or the person’s manager can see their accounts","path":"/business-made/journeys/accounts/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Employee not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee not found","path":"/business-made/journeys/accounts/{employeeId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"A person's accounts in other apps","description":"Every account the person has in other apps — created automatically by provisioning or recorded by hand on a journey task — with provider, app, email, external id, status and groups. HR, an admin, or the person's manager.\n\n#### Signature\n\n```http\nGET /business-made/journeys/accounts/{employeeId} (employeeId: string) -> `{ data:[{key, provider, app, label, email, externalId, status, groups, updatedAt, note, by, automated}], employeeId, name }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | employee-not-found | Employee not found | No employee has that id. | — |\n| `403` | hr-only | Only HR, an admin or the person’s manager can see their accounts | The caller is none of those. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/cohorts":{"get":{"operationId":"JourneyLifecycleController_listCohorts","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ data:[{cohortId, name, event, startDate, lastStartDate, journeys, cancelled, open, complete, atRisk, counts, pct, createdAt}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/cohorts","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List cohorts","description":"Groups of people started together (a class of new hires), newest first, each with its progress: journeys, open, complete, cancelled, at risk, task counts and % done. HR or admin only.\n\n#### Signature\n\n```http\nGET /business-made/journeys/cohorts () -> `{ data:[{cohortId, name, event, startDate, lastStartDate, journeys, cancelled, open, complete, atRisk, counts, pct, createdAt}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"]},"post":{"operationId":"JourneyLifecycleController_startCohort","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ cohortId, name, queued: false, created, failed }` — or `{ cohortId, name, queued: true, people, runAt }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Give the cohort a name — `name` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Give the cohort a name","path":"/business-made/journeys/cohorts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/cohorts","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Start a cohort","description":"Starts the same journey (`event`, default onboarding) for up to 500 people at once, optionally with chosen templates, an effective date, one buddy and a welcome page for all. Up to 10 people start inline; a larger cohort is queued as a one-off job and the response says when it runs (`queued: true`, `runAt`). HR or admin only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/cohorts (body) -> `{ cohortId, name, queued: false, created, failed }` — or `{ cohortId, name, queued: true, people, runAt }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `400` | name-required | Give the cohort a name | `name` is empty. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","employeeIds"],"properties":{"name":{"type":"string"},"event":{"type":"string","default":"onboarding"},"employeeIds":{"type":"array","items":{"type":"string"},"maxItems":500},"templateIds":{"type":"array","items":{"type":"string"}},"effectiveDate":{"type":"string"},"buddyId":{"type":"string"},"welcome":{"type":"object","additionalProperties":true}}},"example":{"name":"October line cooks","employeeIds":["EMP-1043","EMP-1044"],"buddyId":"EMP-1001"}}}}}},"/business-made/journeys/cohorts/{cohortId}":{"get":{"operationId":"JourneyLifecycleController_cohort","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"cohortId","required":true,"in":"path","schema":{"type":"string"}},{"in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ cohort, queue, journeys }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/cohorts/{cohortId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Cohort not found — No journeys and no queued start carry that cohort id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Cohort not found","path":"/business-made/journeys/cohorts/{cohortId}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"One cohort with its journeys","description":"The cohort summary, the state of its queued start when it was queued (`queue: { status: queued | done, runAt, result, people }`), and every journey in it (planned, in progress, complete or cancelled). HR or admin only.\n\n#### Signature\n\n```http\nGET /business-made/journeys/cohorts/{cohortId} (undefined: string) -> `{ cohort, queue, journeys }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `404` | cohort-not-found | Cohort not found | No journeys and no queued start carry that cohort id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/cohorts/{cohortId}/cancel":{"post":{"operationId":"JourneyLifecycleController_cancelCohort","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"cohortId","required":true,"in":"path","schema":{"type":"string"}},{"in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"`{ cohortId, cancelled, failed:[{journeyId, message}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"reason is required — `reason` is empty.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"reason is required","path":"/business-made/journeys/cohorts/{cohortId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/cohorts/{cohortId}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Cancel a cohort","description":"Cancels every open journey in the cohort with the reason. Journeys that fail to cancel are reported, not fatal. HR or admin only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/cohorts/{cohortId}/cancel (undefined: string, body) -> `{ cohortId, cancelled, failed:[{journeyId, message}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `400` | reason-required | reason is required | `reason` is empty. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}},"example":{"reason":"Opening postponed"}}}}}},"/business-made/journeys/cohorts/{cohortId}/reschedule":{"post":{"operationId":"JourneyLifecycleController_rescheduleCohort","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"cohortId","required":true,"in":"path","schema":{"type":"string"}},{"in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"`{ cohortId, moved, failed }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"startDate must be YYYY-MM-DD — `startDate` is not YYYY-MM-DD.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"startDate must be YYYY-MM-DD","path":"/business-made/journeys/cohorts/{cohortId}/reschedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/cohorts/{cohortId}/reschedule","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Move a cohort's start date","description":"Moves everyone's start date: each hire's `employment.startDate` changes and their journey re-plans from it. People who fail are reported. HR or admin only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/cohorts/{cohortId}/reschedule (undefined: string, body) -> `{ cohortId, moved, failed }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `400` | start-date-invalid | startDate must be YYYY-MM-DD | `startDate` is not YYYY-MM-DD. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["startDate"],"properties":{"startDate":{"type":"string","format":"date"}}},"example":{"startDate":"2026-10-19"}}}}}},"/business-made/journeys/rehire":{"post":{"operationId":"JourneyLifecycleController_rehire","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The journey detail (as GET /business-made/journeys/{id})","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"employeeId is required — `employeeId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"employeeId is required","path":"/business-made/journeys/rehire","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/rehire","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Employee EMP-0877 not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee EMP-0877 not found","path":"/business-made/journeys/rehire","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Ana Ruiz has not left — only a former employee can be rehired — The person is still employed.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Ana Ruiz has not left — only a former employee can be rehired","path":"/business-made/journeys/rehire","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Rehire a former employee","description":"For someone who has left (terminated, or inactive with an end date): records their previous stint in the employment history, resets the record (status inactive until day one, new start and hire date, end date cleared, any changed title / department / location / supervisor / employment type applied) and starts a `rehire` journey. Returns the journey detail. HR or admin only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/rehire (body) -> The journey detail (as GET /business-made/journeys/{id})\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `400` | employee-required | employeeId is required | `employeeId` is missing. | — |\n| `404` | employee-not-found | Employee EMP-0877 not found | No employee has that id. | — |\n| `409` | not-a-leaver | Ana Ruiz has not left — only a former employee can be rehired | The person is still employed. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["employeeId","startDate"],"properties":{"employeeId":{"type":"string"},"startDate":{"type":"string","format":"date"},"jobTitle":{"type":"string"},"department":{"type":"string"},"location":{"type":"string"},"supervisor":{"type":"string"},"employmentType":{"type":"string"},"templateIds":{"type":"array","items":{"type":"string"}}}},"example":{"employeeId":"EMP-0877","startDate":"2026-11-02","location":"harbor-grill"}}}}}},"/business-made/journeys/transfer":{"post":{"operationId":"JourneyLifecycleController_transfer","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The journey detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"employeeId is required — `employeeId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"employeeId is required","path":"/business-made/journeys/transfer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/transfer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"A former employee cannot be transferred — rehire them — The person has left.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"A former employee cannot be transferred — rehire them","path":"/business-made/journeys/transfer","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Transfer or change a role","description":"Starts a `transfer` journey (when the location changes) or a `role_change` journey (department, title or supervisor only), applied at `effectiveDate` by the journey. Only fields that actually differ count. Returns the journey detail. HR or admin only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/transfer (body) -> The journey detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `400` | employee-required | employeeId is required | `employeeId` is missing. | — |\n| `409` | terminated | A former employee cannot be transferred — rehire them | The person has left. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["employeeId","effectiveDate"],"properties":{"employeeId":{"type":"string"},"effectiveDate":{"type":"string"},"location":{"type":"string"},"department":{"type":"string"},"jobTitle":{"type":"string"},"supervisor":{"type":"string"},"templateIds":{"type":"array","items":{"type":"string"}}}},"example":{"employeeId":"EMP-1043","effectiveDate":"2026-11-01","location":"dockside"}}}}}},"/business-made/journeys/employee/{employeeId}/history":{"get":{"operationId":"JourneyLifecycleController_history","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"employeeId","required":true,"in":"path","schema":{"type":"string"},"description":"Employee code or bm_employee sk.","example":"EMP-1043"}],"responses":{"200":{"description":"`{ employeeId, employmentHistory, journeys, equipment }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/employee/{employeeId}/history","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Employee EMP-1043 not found — No employee has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Employee EMP-1043 not found","path":"/business-made/journeys/employee/{employeeId}/history","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"A person's employment history","description":"Their earlier stints and changes (`employmentHistory`), every journey they have had, and the equipment issued to them. HR or admin only.\n\n#### Signature\n\n```http\nGET /business-made/journeys/employee/{employeeId}/history (employeeId: string) -> `{ employeeId, employmentHistory, journeys, equipment }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `404` | employee-not-found | Employee EMP-1043 not found | No employee has that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/{id}/welcome":{"put":{"operationId":"JourneyLifecycleController_welcome","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Journey id (bm_journey sk).","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"200":{"description":"The journey detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/{id}/welcome","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Set a journey's welcome page","description":"What the hire sees before day one: `message`, `arriveTime`, `arriveAt`, `dressCode`, `whatToBring`, `parking`, `contactName`, `contactPhone` (sent flat or under `welcome`; other fields are ignored). Returns the journey detail. HR or admin only.\n\n#### Signature\n\n```http\nPUT /business-made/journeys/{id}/welcome (id: string, body) -> The journey detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"arriveTime":{"type":"string"},"arriveAt":{"type":"string"},"dressCode":{"type":"string"},"whatToBring":{"type":"string"},"parking":{"type":"string"},"contactName":{"type":"string"},"contactPhone":{"type":"string"}}},"example":{"message":"Welcome to the team!","arriveTime":"08:30","arriveAt":"Back door by the loading dock","dressCode":"Black non-slip shoes"}}}}}},"/business-made/journeys/{id}/buddy":{"put":{"operationId":"JourneyLifecycleController_buddy","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Journey id (bm_journey sk).","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"200":{"description":"The journey detail","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"buddyId is required — `buddyId` is missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"buddyId is required","path":"/business-made/journeys/{id}/buddy","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/{id}/buddy","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This journey is closed — The journey is complete or cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This journey is closed","path":"/business-made/journeys/{id}/buddy","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Set a journey's buddy","description":"Names the buddy. Open tasks assigned to the buddy move to the new one; tasks already done stay with whoever did them. Returns the journey detail. HR or admin only.\n\n#### Signature\n\n```http\nPUT /business-made/journeys/{id}/buddy (id: string, body) -> The journey detail\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `400` | buddy-required | buddyId is required | `buddyId` is missing. | — |\n| `409` | journey-closed | This journey is closed | The journey is complete or cancelled. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["buddyId"],"properties":{"buddyId":{"type":"string","description":"Employee code or sk."}}},"example":{"buddyId":"EMP-1001"}}}}}},"/business-made/journeys/templates":{"get":{"operationId":"JourneysController_listTemplates","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"event","in":"query","required":false,"schema":{"type":"string"},"example":"onboarding"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"example":"active"},{"name":"search","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ data:[bm_journey_template], total, page, pageSize }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List journey templates","description":"The starter templates (company-wide onboarding — the old six-item checklist plus the work others do —, contractor and offboarding) are seeded the first time.\n\n#### Signature\n\n```http\nGET /business-made/journeys/templates (event?: string, status?: string, search?: string) -> `{ data:[bm_journey_template], total, page, pageSize }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]},"post":{"operationId":"JourneysController_createTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"bm_journey_template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"task key \"x\" is used twice — The template does not validate.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"task key \"x\" is used twice","path":"/business-made/journeys/templates","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Create a template","description":"Flat bm_journey_template body. Validated: title, event, unique task keys, known stages, dependsOn to known keys without loops, provisioning tasks name an action.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/templates (body) -> bm_journey_template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | template-invalid | task key \"x\" is used twice | The template does not validate. | Fix the listed errors. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/business-made/journeys/templates/{id}":{"get":{"operationId":"JourneysController_getTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Template id (bm_journey_template sk).","example":"66f0c3a1e4b0a1b2c3d4e5f7"}],"responses":{"200":{"description":"bm_journey_template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Journey template not found — No template has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey template not found","path":"/business-made/journeys/templates/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a template","description":"#### Signature\n\n```http\nGET /business-made/journeys/templates/{id} (id: string) -> bm_journey_template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | template-not-found | Journey template not found | No template has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]},"put":{"operationId":"JourneysController_updateTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Template id (bm_journey_template sk).","example":"66f0c3a1e4b0a1b2c3d4e5f7"}],"responses":{"200":{"description":"bm_journey_template","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Journey template not found — No template has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey template not found","path":"/business-made/journeys/templates/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Template code ONB-KITCHEN is already used by \"Kitchen onboarding\" — Another template has that code.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Template code ONB-KITCHEN is already used by \"Kitchen onboarding\"","path":"/business-made/journeys/templates/{id}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Update a template","description":"Flat body merged over the stored template. Changing an active template’s tasks bumps its version; open journeys pick it up on their next re-plan.\n\n#### Signature\n\n```http\nPUT /business-made/journeys/templates/{id} (id: string, body) -> bm_journey_template\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | template-not-found | Journey template not found | No template has that id. | — |\n| `409` | template-code-taken | Template code ONB-KITCHEN is already used by \"Kitchen onboarding\" | Another template has that code. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}},"delete":{"operationId":"JourneysController_removeTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Template id (bm_journey_template sk).","example":"66f0c3a1e4b0a1b2c3d4e5f7"}],"responses":{"200":{"description":"`{ id, deleted, retired? }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Delete a template","description":"A template open journeys still use is retired instead of deleted.\n\n#### Signature\n\n```http\nDELETE /business-made/journeys/templates/{id} (id: string) -> `{ id, deleted, retired? }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/templates/{id}/preview":{"post":{"operationId":"JourneysController_previewTemplate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Template id (bm_journey_template sk).","example":"66f0c3a1e4b0a1b2c3d4e5f7"}],"responses":{"201":{"description":"`{ tasks:[TaskView with dueDate], matches, employee }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Preview a template for a person","description":"#### Signature\n\n```http\nPOST /business-made/journeys/templates/{id}/preview (id: string, body) -> `{ tasks:[TaskView with dueDate], matches, employee }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["employeeId"],"properties":{"employeeId":{"type":"string"}}}}}}}},"/business-made/journeys/my-tasks":{"get":{"operationId":"JourneysController_myTasks","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"description":"`open` (default: todo, in_progress, blocked), `done`, `skipped`, `all`, or a comma list.","example":"open"}],"responses":{"200":{"description":"`{ data:[TaskView & { journeyId, journeyTitle, event, employeeName }] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"My journey tasks","description":"Tasks assigned to the caller across every journey (as the hire, manager, HR, IT, buddy, payroll…), overdue first.\n\n#### Signature\n\n```http\nGET /business-made/journeys/my-tasks (status?: string) -> `{ data:[TaskView & { journeyId, journeyTitle, event, employeeName }] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/team":{"get":{"operationId":"JourneysController_team","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ data:[{employeeId, name, position, location, readiness:{overall, byGate}, blocking:[{kind, title, status, dueDate}], journey:{id, event, counts, stageNow, startDate, atRisk, riskReasons}|null}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"My team","description":"The caller’s reports (supervisor / direct reports): readiness, what blocks them, and their open journey.\n\n#### Signature\n\n```http\nGET /business-made/journeys/team () -> `{ data:[{employeeId, name, position, location, readiness:{overall, byGate}, blocking:[{kind, title, status, dueDate}], journey:{id, event, counts, stageNow, startDate, atRisk, riskReasons}\\|null}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/reports":{"get":{"operationId":"JourneysController_reports","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"event","in":"query","required":false,"schema":{"type":"string"},"example":"onboarding"},{"name":"location","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Start date from.","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ totals:{journeys, complete, offerToReadyDays, timeToProductiveDays, onTimeRate, neverStarted}, byLocation:[…], byTemplate:[…], overdueByOwner:[{name, relation, overdue}], neverStarted:[…] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Onboarding reports","description":"Offer-to-ready days (offer accepted → required day-1 tasks done), time to productive (start → journey complete), on-time task completion, never-started hires, overdue by owner — overall, per location and per template. HR or admin only.\n\n#### Signature\n\n```http\nGET /business-made/journeys/reports (event?: string, location?: string, from?: string, to?: string) -> `{ totals:{journeys, complete, offerToReadyDays, timeToProductiveDays, onTimeRate, neverStarted}, byLocation:[…], byTemplate:[…], overdueByOwner:[{name, relation, overdue}], neverStarted:[…] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/migrate-checklists":{"post":{"operationId":"JourneysController_migrate","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"`{ migrated, failed:[{employeeId, message}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Move old onboarding checklists onto journeys","description":"Every employee with an in-progress six-item checklist and no journey gets an onboarding journey; completed and skipped items carry over. The old /business-made/onboarding endpoints do this per person on first touch.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/migrate-checklists () -> `{ migrated, failed:[{employeeId, message}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/tasks/{taskId}/complete":{"post":{"operationId":"JourneysController_complete","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (task sk).","example":"66f0c3a1e4b0a1b2c3d4e5f8"}],"responses":{"201":{"description":"The TaskView.","content":{"application/json":{"schema":{"type":"object","description":"A journey task as people read it. Everything here is computed on the server.","properties":{"taskId":{"type":"string"},"key":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["form","esign","document_upload","policy_ack","course","quiz","meeting","shadow_shift","provisioning","approval","signoff","checklist","custom"]},"stage":{"type":"string"},"assignee":{"type":"object","properties":{"relation":{"type":"string"},"role":{"type":"string"},"userId":{"type":"string"},"name":{"type":"string"}}},"dueDate":{"type":"string","format":"date-time"},"dueState":{"type":"string","enum":["overdue","due_soon"],"nullable":true},"dueLabel":{"type":"string","example":"Due in 3 days"},"status":{"type":"string","enum":["todo","in_progress","done","skipped","blocked"]},"required":{"type":"boolean"},"dependsOn":{"type":"array","items":{"type":"string"}},"evidence":{"type":"object","properties":{"completesOn":{"type":"string"},"formKey":{"type":"string"},"policyId":{"type":"string"},"documentType":{"type":"string"},"courseId":{"type":"string"},"ref":{"type":"object","additionalProperties":true}}},"courseUrl":{"type":"string"},"meeting":{"type":"object","properties":{"startTime":{"type":"string","nullable":true},"withName":{"type":"string","nullable":true},"location":{"type":"string","nullable":true}}},"route":{"type":"string","nullable":true,"description":"Where to go to do it. null = inside the task (policy, upload and signature are captured by the complete call)."},"employeeId":{"type":"string"},"requirementId":{"type":"string"},"completedAt":{"type":"string"},"blockedReason":{"type":"string","example":"Waiting for: Add to payroll"},"canComplete":{"type":"boolean","description":"This caller may complete it now."}}}}}},"400":{"description":"Say what was done — a note is required to mark an account task — The evidence in the body does not satisfy the task type. The body carries a `reason` naming the check: note-required, answer-required, answer-invalid, fields-required, files-required, signature-required, card-required, serial-required, expiry-required, items-required, returns-required, policy-required, policy-changed, document-expired, form-not-found, policy-not-found, accounts-pending (with `pending`), inventory-unavailable, location-required or status-invalid.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Say what was done — a note is required to mark an account task","path":"/business-made/journeys/tasks/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"This task is not yours — Caller is not an assignee, HR or admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"This task is not yours","path":"/business-made/journeys/tasks/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Journey task not found — No journey task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey task not found","path":"/business-made/journeys/tasks/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"Waiting for: … — A prerequisite is not done.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Waiting for: …","path":"/business-made/journeys/tasks/{taskId}/complete","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Complete a task","description":"Tasks that close from evidence cannot be ticked: if the evidence exists they close, otherwise 409 `completes-from-evidence` (HR can override with a note). The body can carry the evidence itself, and the platform writes the real record:\n- policy: `{datatype:\"bm_policy\", id, version}` → the bm_policy_acknowledgement (409 `policy-changed` if a newer version exists);\n- upload: `{datatype:\"file\", documentType, files:[{path,url}], expiresAt}` → the bm_employee_document (certifications need an unexpired expiry);\n- e-sign: `{datatype:\"signature\", typedName, image, consent:true}` → the signed bm_employee_document, with time, IP and user agent.\nA requirement-linked task then records readiness evidence.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/tasks/{taskId}/complete (taskId: string, body) -> The TaskView.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | task-blocked | Waiting for: … | A prerequisite is not done. | Finish the prerequisite first. |\n| `403` | not-assignee | This task is not yours | Caller is not an assignee, HR or admin. | — |\n| `404` | task-not-found | Journey task not found | No journey task has that id. | — |\n| `400` | evidence-invalid | Say what was done — a note is required to mark an account task | The evidence in the body does not satisfy the task type. The body carries a `reason` naming the check: note-required, answer-required, answer-invalid, fields-required, files-required, signature-required, card-required, serial-required, expiry-required, items-required, returns-required, policy-required, policy-changed, document-expired, form-not-found, policy-not-found, accounts-pending (with `pending`), inventory-unavailable, location-required or status-invalid. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"},"evidence":{"type":"object","additionalProperties":true}}},"example":{"evidence":{"datatype":"bm_policy","id":"66f0c3a1e4b0a1b2c3d4e5f9","version":4}}}}}}},"/business-made/journeys/tasks/{taskId}/skip":{"post":{"operationId":"JourneysController_skip","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taskId","required":true,"in":"path","schema":{"type":"string"},"description":"Task id (task sk).","example":"66f0c3a1e4b0a1b2c3d4e5f8"}],"responses":{"201":{"description":"The TaskView.","content":{"application/json":{"schema":{"type":"object","description":"A journey task as people read it. Everything here is computed on the server.","properties":{"taskId":{"type":"string"},"key":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["form","esign","document_upload","policy_ack","course","quiz","meeting","shadow_shift","provisioning","approval","signoff","checklist","custom"]},"stage":{"type":"string"},"assignee":{"type":"object","properties":{"relation":{"type":"string"},"role":{"type":"string"},"userId":{"type":"string"},"name":{"type":"string"}}},"dueDate":{"type":"string","format":"date-time"},"dueState":{"type":"string","enum":["overdue","due_soon"],"nullable":true},"dueLabel":{"type":"string","example":"Due in 3 days"},"status":{"type":"string","enum":["todo","in_progress","done","skipped","blocked"]},"required":{"type":"boolean"},"dependsOn":{"type":"array","items":{"type":"string"}},"evidence":{"type":"object","properties":{"completesOn":{"type":"string"},"formKey":{"type":"string"},"policyId":{"type":"string"},"documentType":{"type":"string"},"courseId":{"type":"string"},"ref":{"type":"object","additionalProperties":true}}},"courseUrl":{"type":"string"},"meeting":{"type":"object","properties":{"startTime":{"type":"string","nullable":true},"withName":{"type":"string","nullable":true},"location":{"type":"string","nullable":true}}},"route":{"type":"string","nullable":true,"description":"Where to go to do it. null = inside the task (policy, upload and signature are captured by the complete call)."},"employeeId":{"type":"string"},"requirementId":{"type":"string"},"completedAt":{"type":"string"},"blockedReason":{"type":"string","example":"Waiting for: Add to payroll"},"canComplete":{"type":"boolean","description":"This caller may complete it now."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"A required task can only be skipped by HR — The task is required and the caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"A required task can only be skipped by HR","path":"/business-made/journeys/tasks/{taskId}/skip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Journey task not found — No journey task has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey task not found","path":"/business-made/journeys/tasks/{taskId}/skip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"409":{"description":"This task is already closed — The task is done, skipped or cancelled.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"This task is already closed","path":"/business-made/journeys/tasks/{taskId}/skip","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Skip a task","description":"Optional tasks: the assignee or HR. Required tasks: HR only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/tasks/{taskId}/skip (taskId: string, body) -> The TaskView.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | task-not-found | Journey task not found | No journey task has that id. | — |\n| `409` | task-closed | This task is already closed | The task is done, skipped or cancelled. | — |\n| `403` | required-task | A required task can only be skipped by HR | The task is required and the caller is not HR or an admin. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}}}}}}},"/business-made/journeys":{"get":{"operationId":"JourneysController_list","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"event","in":"query","required":false,"description":"onboarding, contractor, offboarding, role_change, transfer… (comma list).","schema":{"type":"string"},"example":"onboarding"},{"name":"status","in":"query","required":false,"description":"planned, in_progress, complete, cancelled, or `open`. Default: all but cancelled.","schema":{"type":"string"},"example":"open"},{"name":"location","in":"query","required":false,"schema":{"type":"string"},"example":"harbor-grill"},{"name":"atRisk","in":"query","required":false,"schema":{"type":"boolean"},"example":true},{"name":"search","in":"query","required":false,"schema":{"type":"string"},"example":"Luis"},{"name":"page","in":"query","required":false,"description":"0-based.","schema":{"type":"integer"},"example":0},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer"},"example":50}],"responses":{"200":{"description":"`{ data:[{id, employeeId, employeeName, event, status, startDate, location, stageNow, counts:{done,total,overdue,requiredDone,requiredTotal}, atRisk, riskReasons:[string], riskCodes:[string]}], total, page, pageSize }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"The journeys board","description":"Every journey, at-risk first then by start date. At risk = start within 3 days with paperwork missing, overdue required tasks, or upcoming shifts the person is blocked from by readiness. HR or admin only.\n\n#### Signature\n\n```http\nGET /business-made/journeys (event?: string, status?: string, location?: string, atRisk?: boolean, search?: string, page?: integer, pageSize?: integer) -> `{ data:[{id, employeeId, employeeName, event, status, startDate, location, stageNow, counts:{done,total,overdue,requiredDone,requiredTotal}, atRisk, riskReasons:[string], riskCodes:[string]}], total, page, pageSize }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]},"post":{"operationId":"JourneysController_start","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"The journey detail (same shape as GET /business-made/journeys/{id}).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"An offboarding needs its effectiveDate — Offboarding without an end time.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An offboarding needs its effectiveDate","path":"/business-made/journeys","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"already has an open journey — An open journey for this person and event exists.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"already has an open journey","path":"/business-made/journeys","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"No active template matches — No active template for the event matches the person.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"No active template matches","path":"/business-made/journeys","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Start a journey","description":"Creates the journey and its tasks from the templates that match the person (or `templateIds`). Due dates count from the start date, or for offboarding/changes from `effectiveDate`. A task marked for the exact effective time (offboarding revoke) is queued as its own one-off job at that instant; the sweep (every 10 minutes by default, see readiness settings) moves everything else and escalates overdue tasks to the manager, then HR, after the days set in `journeyEscalation`. An onboarding for employment type `contract` becomes a contractor journey when a contractor template exists. HR or admin only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys (body) -> The journey detail (same shape as GET /business-made/journeys/{id}).\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | journey-exists | already has an open journey | An open journey for this person and event exists. | Open it, or cancel it first. |\n| `422` | no-template | No active template matches | No active template for the event matches the person. | Activate or widen a template, or pass templateIds. |\n| `400` | effective-date-required | An offboarding needs its effectiveDate | Offboarding without an end time. | Send effectiveDate. |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["employeeId","event"],"properties":{"employeeId":{"type":"string"},"event":{"type":"string"},"effectiveDate":{"type":"string","description":"Date or exact date-time; required for offboarding unless the employee has an end date."},"templateIds":{"type":"array","items":{"type":"string"}},"buddyId":{"type":"string"}}},"example":{"employeeId":"EMP-1043","event":"onboarding"}}}}}},"/business-made/journeys/{id}":{"get":{"operationId":"JourneysController_detail","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Journey id (bm_journey sk).","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"200":{"description":"`{ journey:{…row, replans, templateIds, buddyId, source}, stages:[{stage, title, dueDate, status, tasks:[TaskView]}], stageNow, counts, atRisk, riskReasons, employee }`","content":{"application/json":{"schema":{"type":"object","properties":{"stages":{"type":"array","items":{"type":"object","properties":{"tasks":{"type":"array","items":{"type":"object","description":"A journey task as people read it. Everything here is computed on the server.","properties":{"taskId":{"type":"string"},"key":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["form","esign","document_upload","policy_ack","course","quiz","meeting","shadow_shift","provisioning","approval","signoff","checklist","custom"]},"stage":{"type":"string"},"assignee":{"type":"object","properties":{"relation":{"type":"string"},"role":{"type":"string"},"userId":{"type":"string"},"name":{"type":"string"}}},"dueDate":{"type":"string","format":"date-time"},"dueState":{"type":"string","enum":["overdue","due_soon"],"nullable":true},"dueLabel":{"type":"string","example":"Due in 3 days"},"status":{"type":"string","enum":["todo","in_progress","done","skipped","blocked"]},"required":{"type":"boolean"},"dependsOn":{"type":"array","items":{"type":"string"}},"evidence":{"type":"object","properties":{"completesOn":{"type":"string"},"formKey":{"type":"string"},"policyId":{"type":"string"},"documentType":{"type":"string"},"courseId":{"type":"string"},"ref":{"type":"object","additionalProperties":true}}},"courseUrl":{"type":"string"},"meeting":{"type":"object","properties":{"startTime":{"type":"string","nullable":true},"withName":{"type":"string","nullable":true},"location":{"type":"string","nullable":true}}},"route":{"type":"string","nullable":true,"description":"Where to go to do it. null = inside the task (policy, upload and signature are captured by the complete call)."},"employeeId":{"type":"string"},"requirementId":{"type":"string"},"completedAt":{"type":"string"},"blockedReason":{"type":"string","example":"Waiting for: Add to payroll"},"canComplete":{"type":"boolean","description":"This caller may complete it now."}}}}}}}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Journey not found — No journey has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey not found","path":"/business-made/journeys/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"A journey with its stages and tasks","description":"#### Signature\n\n```http\nGET /business-made/journeys/{id} (id: string) -> `{ journey:{…row, replans, templateIds, buddyId, source}, stages:[{stage, title, dueDate, status, tasks:[TaskView]}], stageNow, counts, atRisk, riskReasons, employee }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | journey-not-found | Journey not found | No journey has that id. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/{id}/replan":{"post":{"operationId":"JourneysController_replan","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Journey id (bm_journey sk).","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"The journey detail.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/{id}/replan","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Journey not found — No journey has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey not found","path":"/business-made/journeys/{id}/replan","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Re-plan a journey","description":"Re-matches templates against the person now, adds and removes tasks, moves due dates with the start/effective date and re-resolves who does each open task. Happens on its own when role, department, location, start date or employment type change. Exact-time tasks (offboarding revoke) get their queued one-off job moved to the new time. HR or admin only.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/{id}/replan (id: string) -> The journey detail.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `404` | journey-not-found | Journey not found | No journey has that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/business-made/journeys/{id}/cancel":{"post":{"operationId":"JourneysController_cancel","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Journey id (bm_journey sk).","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"The journey detail.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Journey not found — No journey has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey not found","path":"/business-made/journeys/{id}/cancel","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Cancel a journey","description":"Cancels the open tasks and removes their queued exact-time jobs.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/{id}/cancel (id: string, body) -> The journey detail.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `404` | journey-not-found | Journey not found | No journey has that id. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string"}}},"example":{"reason":"Offer withdrawn"}}}}}},"/business-made/journeys/{id}/unlock-preboarding":{"post":{"operationId":"JourneysController_unlock","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"Journey id (bm_journey sk).","example":"66f0c3a1e4b0a1b2c3d4e5f6"}],"responses":{"201":{"description":"The journey detail.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Only a hire’s first journey has pre-boarding — The journey is not an onboarding.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Only a hire’s first journey has pre-boarding","path":"/business-made/journeys/{id}/unlock-preboarding","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"description":"Only HR or an admin can do this — The caller is not HR or an admin.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":403,"error":"Only HR or an admin can do this","path":"/business-made/journeys/{id}/unlock-preboarding","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"404":{"description":"Journey not found — No journey has that id.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"Journey not found","path":"/business-made/journeys/{id}/unlock-preboarding","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Open pre-boarding by hand","description":"For templates whose pre-boarding opens manually, or to open it early. The hire can then sign in before day 1 and sees only their onboarding.\n\n#### Signature\n\n```http\nPOST /business-made/journeys/{id}/unlock-preboarding (id: string) -> The journey detail.\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |\n| `404` | journey-not-found | Journey not found | No journey has that id. | — |\n| `400` | no-preboarding | Only a hire’s first journey has pre-boarding | The journey is not an onboarding. | — |\n\nPlus the standard platform errors: `401`, `429`, `500`.","tags":["Business Made - Journeys"]}},"/staff-portal/onboarding":{"get":{"operationId":"JourneysStaffController_onboarding","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"200":{"description":"`{ journey|null, counts, myCounts, stageNow, preboarding, stages:[{stage,title,dueDate,status,tasks:[TaskView]}], nextTasks:[TaskView], othersForMe:[{title, assigneeName, status, dueDate, meeting?}] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"My onboarding","description":"The caller’s onboarding (or contractor) journey: their own tasks by stage (before day 1 only the pre-boarding ones), what others are doing for them, the next things to do. Looks at the evidence first, so what they just did shows as done.\n\n#### Signature\n\n```http\nGET /staff-portal/onboarding () -> `{ journey\\|null, counts, myCounts, stageNow, preboarding, stages:[{stage,title,dueDate,status,tasks:[TaskView]}], nextTasks:[TaskView], othersForMe:[{title, assigneeName, status, dueDate, meeting?}] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Staff portal"]}},"/staff-portal/tasks":{"get":{"operationId":"JourneysStaffController_tasks","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"status","required":false,"in":"query","schema":{"type":"string"},"example":"open"}],"responses":{"200":{"description":"`{ data:[TaskView] }`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"My own journey tasks","description":"Only the caller’s tasks as the person on the journey.\n\n#### Signature\n\n```http\nGET /staff-portal/tasks (status?: string) -> `{ data:[TaskView] }`\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","tags":["Staff portal"]}},"/business-made/efile/connections/{program}":{"get":{"operationId":"EfileController_getConnection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"program","required":true,"in":"path","schema":{"type":"string","enum":["iris","tinm","eservices"]},"description":"iris = IRIS 1099 filing; tinm = TIN Matching; eservices = other e-Services APIs."}],"responses":{"200":{"description":"ConnectionView","content":{"application/json":{"schema":{"type":"object","properties":{"program":{"type":"string"},"configured":{"type":"boolean"},"clientId":{"type":"string"},"userId":{"type":"string"},"kid":{"type":"string"},"env":{"type":"string","enum":["test","prod"]},"allowProduction":{"type":"boolean","description":"Off by default; while off, production is refused."},"tcc":{"type":"string"},"hasPrivateKey":{"type":"boolean"},"publicKeyThumbprint":{"type":"string","description":"RFC 7638 SHA-256 thumbprint of the public key"},"keyBits":{"type":"number"},"tokenUrl":{"type":"string"},"missing":{"type":"array","items":{"type":"string"},"description":"Fields still needed before a token can be requested"},"lastTest":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"IRS API connection","description":"Owner / ConfigAdmin / RootAdmin. The private key is never returned: `hasPrivateKey` and `publicKeyThumbprint` show which key is stored.\n\n#### Signature\n\n```http\nGET /business-made/efile/connections/{program} (program: string) -> ConnectionView\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."},"put":{"operationId":"EfileController_saveConnection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"program","required":true,"in":"path","schema":{"type":"string","enum":["iris","tinm","eservices"]},"description":"iris = IRIS 1099 filing; tinm = TIN Matching; eservices = other e-Services APIs."}],"responses":{"200":{"description":"ConnectionView","content":{"application/json":{"schema":{"type":"object","properties":{"program":{"type":"string"},"configured":{"type":"boolean"},"clientId":{"type":"string"},"userId":{"type":"string"},"kid":{"type":"string"},"env":{"type":"string","enum":["test","prod"]},"allowProduction":{"type":"boolean","description":"Off by default; while off, production is refused."},"tcc":{"type":"string"},"hasPrivateKey":{"type":"boolean"},"publicKeyThumbprint":{"type":"string","description":"RFC 7638 SHA-256 thumbprint of the public key"},"keyBits":{"type":"number"},"tokenUrl":{"type":"string"},"missing":{"type":"array","items":{"type":"string"},"description":"Fields still needed before a token can be requested"},"lastTest":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"The private key could not be read — Not a PEM RSA private key.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"The private key could not be read","path":"/business-made/efile/connections/{program}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"Storing an IRS private key needs FINANCE_ENCRYPTION_KEY on the server — The server cannot seal the key.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"Storing an IRS private key needs FINANCE_ENCRYPTION_KEY on the server","path":"/business-made/efile/connections/{program}","method":"PUT","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Save an IRS API connection","description":"Fields: clientId, userId (full e-Services User ID from the Consent App), kid, tcc, env (test | prod), allowProduction, privateKeyPem. `privateKeyPem` is write-only: sent = replaced (must be an RSA PEM), omitted = kept, `clearPrivateKey: true` = removed. The key is sealed with FINANCE_ENCRYPTION_KEY. `env: prod` is refused unless `allowProduction` is true; turning `allowProduction` off returns the connection to test. Saving clears the cached token and the last test result.\n\n#### Signature\n\n```http\nPUT /business-made/efile/connections/{program} (program: string, body) -> ConnectionView\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | EFILE_BAD_PRIVATE_KEY | The private key could not be read | Not a PEM RSA private key. | — |\n| `412` | ENCRYPTION_KEY_MISSING | Storing an IRS private key needs FINANCE_ENCRYPTION_KEY on the server | The server cannot seal the key. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"clientId":"4d81a0c6-…","userId":"dasmith-345870","kid":"my-kid","env":"test","privateKeyPem":"-----BEGIN PRIVATE KEY-----…"}}}}}},"/business-made/efile/connections/{program}/test":{"post":{"operationId":"EfileController_testConnection","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"program","required":true,"in":"path","schema":{"type":"string","enum":["iris","tinm","eservices"]},"description":"iris = IRIS 1099 filing; tinm = TIN Matching; eservices = other e-Services APIs."}],"responses":{"201":{"description":"TestResult","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"env":{"type":"string","enum":["test","prod"]},"stage":{"type":"string","enum":["config","client_jwt","user_jwt","consent","network","ok"]},"errorCode":{"type":"string","description":"IRS code, e.g. ESRV306 (key/kid not registered on the client), ESRV202 (User ID not recognised), ESRV124 (consent not granted for this environment)"},"message":{"type":"string"},"scope":{"type":"string","description":"Scopes the IRS granted, e.g. \"esrv-alt sor tds tinm\""},"expiresIn":{"type":"number"},"at":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Test an IRS API connection","description":"Signs the client and user RS256 JWTs (Pub 5718 §3.1.2) and requests a fresh token from the IRS token endpoint of the connection’s environment. Returns the stage that failed and a plain-language message; the result is kept on the connection as `lastTest`.\n\n#### Signature\n\n```http\nPOST /business-made/efile/connections/{program}/test (program: string) -> TestResult\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/efile/submissions":{"get":{"operationId":"EfileController_listSubmissions","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"agency","in":"query","required":false,"schema":{"type":"string","enum":["irs","ssa"]}},{"name":"program","in":"query","required":false,"schema":{"type":"string","enum":["iris","mef","tinm","efw2"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","ready","submitted","accepted","partially_accepted","rejected","error"]}},{"name":"taxYear","in":"query","required":false,"schema":{"type":"number"}},{"name":"formType","in":"query","required":false,"schema":{"type":"string"}},{"name":"keyword","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"number","default":1}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"number","default":25}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"createdate"}},{"name":"sortType","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"{ data: bm_efile_submission[], total, page, pageSize }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"E-file submissions","description":"Paged `bm_efile_submission` records, newest first. Filters and sorting are server-side.\n\n#### Signature\n\n```http\nGET /business-made/efile/submissions (agency?: string, program?: string, status?: string, taxYear?: number, formType?: string, keyword?: string, page?: number, pageSize?: number, sort?: string, sortType?: string) -> { data: bm_efile_submission[], total, page, pageSize }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/efile/submissions/{id}":{"get":{"operationId":"EfileController_getSubmission","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_efile_submission sk"}],"responses":{"200":{"description":"bm_efile_submission","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"E-file submission not found — No such record.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-file submission not found","path":"/business-made/efile/submissions/{id}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"An e-file submission","description":"#### Signature\n\n```http\nGET /business-made/efile/submissions/{id} (id: string) -> bm_efile_submission\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EFILE_NOT_FOUND | E-file submission not found | No such record. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/efile/submissions/{id}/files/{kind}":{"get":{"operationId":"EfileController_downloadSubmissionFile","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_efile_submission sk"},{"name":"kind","required":true,"in":"path","schema":{"type":"string","enum":["payload","ack"]}}],"responses":{"200":{"description":"The file (attachment)","content":{"application/json":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"This submission has no payload file — Nothing attached, or missing from storage.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"This submission has no payload file","path":"/business-made/efile/submissions/{id}/files/{kind}","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Download a submission’s payload or acknowledgement","description":"The files carry full SSNs/TINs, so they are stored private and served only here, with `Cache-Control: no-store`. `payloadFileUrl` / `ackFileUrl` on the record hold this route.\n\n#### Signature\n\n```http\nGET /business-made/efile/submissions/{id}/files/{kind} (id: string, kind: string) -> The file (attachment)\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EFILE_FILE_NOT_FOUND | This submission has no payload file | Nothing attached, or missing from storage. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/efile/tinm/check":{"post":{"operationId":"TinmController_check","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ env, checked, matched, results: Result[] }","content":{"application/json":{"schema":{"type":"object","properties":{"env":{"type":"string"},"checked":{"type":"number"},"matched":{"type":"number"},"results":{"type":"array","items":{"type":"object","properties":{"payee":{"type":"object","additionalProperties":true},"accountRef":{"type":"string"},"name":{"type":"string"},"tin":{"type":"string","description":"Masked: *****1234"},"tinType":{"type":"string"},"code":{"type":"string","enum":["0","1","2","3","4","5","6","7","8"],"description":"Pub 2108A: 0 match; 1 TIN missing / not 9 digits; 2 TIN not issued; 3 name/TIN do not match; 4 invalid request; 5 duplicate; 6/7/8 matched on SSN / EIN / both (TIN type unknown). null = not sent."},"meaning":{"type":"string"},"matched":{"type":"boolean"},"errors":{"type":"array","items":{"type":"string"}}}}}}}}}},"400":{"description":"An interactive check takes at most 25 payees — More than 25 payees.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"An interactive check takes at most 25 payees","path":"/business-made/efile/tinm/check","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"The IRS TIN Matching connection is not set up — No tinm connection, or fields missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"The IRS TIN Matching connection is not set up","path":"/business-made/efile/tinm/check","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"424":{"description":"The IRS refused the token request — Key, User ID or consent problem.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":424,"error":"The IRS refused the token request","path":"/business-made/efile/tinm/check","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Interactive TIN Matching (up to 25 payees)","description":"Owner / ConfigAdmin / RootAdmin. Each valid payee is checked with the IRS at once; payees without a usable name or 9-digit TIN are reported and not sent. Each answer is written on the payee record as `tinMatch`. Uses the org’s tinm connection (TEST unless the owner allowed production).\n\n#### Signature\n\n```http\nPOST /business-made/efile/tinm/check (body) -> { env, checked, matched, results: Result[] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TINM_TOO_MANY | An interactive check takes at most 25 payees | More than 25 payees. | — |\n| `412` | EFILE_CONNECTION_INCOMPLETE | The IRS TIN Matching connection is not set up | No tinm connection, or fields missing. | — |\n| `424` | IRS_TOKEN_FAILED | The IRS refused the token request | Key, User ID or consent problem. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"payees":{"type":"array","maxItems":25,"items":{"type":"object","description":"A payee record ({ datatype, id }) or a name/TIN typed in ({ name, tin, tinType }). For a record, the server takes the name, TIN and TIN type from it.","properties":{"datatype":{"type":"string","enum":["bm_employee","bm_vendor"]},"id":{"type":"string","description":"Record sk (a contractor may also be given by badge code)"},"name":{"type":"string"},"tin":{"type":"string","description":"9 digits"},"tinType":{"type":"string","enum":["ssn","ein","unknown"]}}}}}},"example":{"payees":[{"datatype":"bm_employee","id":"665f1c…"},{"name":"Example Payee LLC","tin":"000000001","tinType":"ein"}]}}}}}},"/business-made/efile/tinm/bulk":{"post":{"operationId":"TinmController_bulk","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"}],"responses":{"201":{"description":"{ submission, env, sent, notSent: [{ code, message, recordRef }] }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Send the payees to check — No payees and no scope, or the scope has none.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"Send the payees to check","path":"/business-made/efile/tinm/bulk","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"The IRS TIN Matching connection is not set up — No tinm connection, or fields missing.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"The IRS TIN Matching connection is not set up","path":"/business-made/efile/tinm/bulk","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"424":{"description":"The IRS refused the token request — Key, User ID or consent problem.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":424,"error":"The IRS refused the token request","path":"/business-made/efile/tinm/bulk","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Bulk TIN Matching (up to 100,000 payees)","description":"Builds the Pub 2108A request file (TIN TYPE;TIN;NAME;ACCOUNT, one line per payee; duplicates and invalid payees left out and listed), keeps it privately on a new bm_efile_submission (program tinm) and uploads it. `scope: all_1099_payees` takes every contractor and every vendor marked 1099. Results arrive in the IRS SOR mailbox within 24 hours and are collected by the hourly `tinm-results` job or GET …/results.\n\n#### Signature\n\n```http\nPOST /business-made/efile/tinm/bulk (body) -> { submission, env, sent, notSent: [{ code, message, recordRef }] }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `400` | TINM_NO_PAYEES | Send the payees to check | No payees and no scope, or the scope has none. | — |\n| `412` | EFILE_CONNECTION_INCOMPLETE | The IRS TIN Matching connection is not set up | No tinm connection, or fields missing. | — |\n| `424` | IRS_TOKEN_FAILED | The IRS refused the token request | Key, User ID or consent problem. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"payees":{"type":"array","items":{"type":"object","description":"A payee record ({ datatype, id }) or a name/TIN typed in ({ name, tin, tinType }). For a record, the server takes the name, TIN and TIN type from it.","properties":{"datatype":{"type":"string","enum":["bm_employee","bm_vendor"]},"id":{"type":"string","description":"Record sk (a contractor may also be given by badge code)"},"name":{"type":"string"},"tin":{"type":"string","description":"9 digits"},"tinType":{"type":"string","enum":["ssn","ein","unknown"]}}}},"scope":{"type":"string","enum":["all_1099_payees"]},"taxYear":{"type":"number"}}},"example":{"scope":"all_1099_payees","taxYear":2026}}}}}},"/business-made/efile/tinm/submissions/{id}/results":{"get":{"operationId":"TinmController_results","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_efile_submission sk (program tinm)"}],"responses":{"200":{"description":"{ id, status, env, ready, message?, summary: { total, matched, notMatched, byCode }, results: Result[], notSent }","content":{"application/json":{"schema":{"type":"object","properties":{"ready":{"type":"boolean"},"summary":{"type":"object","additionalProperties":true},"results":{"type":"array","items":{"type":"object","properties":{"payee":{"type":"object","additionalProperties":true},"accountRef":{"type":"string"},"name":{"type":"string"},"tin":{"type":"string","description":"Masked: *****1234"},"tinType":{"type":"string"},"code":{"type":"string","enum":["0","1","2","3","4","5","6","7","8"],"description":"Pub 2108A: 0 match; 1 TIN missing / not 9 digits; 2 TIN not issued; 3 name/TIN do not match; 4 invalid request; 5 duplicate; 6/7/8 matched on SSN / EIN / both (TIN type unknown). null = not sent."},"meaning":{"type":"string"},"matched":{"type":"boolean"},"errors":{"type":"array","items":{"type":"string"}}}}}}}}}},"400":{"description":"This submission is not a TIN Matching request — Another program’s submission.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"This submission is not a TIN Matching request","path":"/business-made/efile/tinm/submissions/{id}/results","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"E-file submission not found — No such record.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":404,"error":"E-file submission not found","path":"/business-made/efile/tinm/submissions/{id}/results","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"424":{"description":"The IRS refused access to the e-Services mailbox (SOR) — The user cannot read the SOR mailbox.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":424,"error":"The IRS refused access to the e-Services mailbox (SOR)","path":"/business-made/efile/tinm/submissions/{id}/results","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Results of a bulk TIN Matching request","description":"While the request waits (status submitted), the IRS SOR mailbox is checked first. Once the result file is in, it is stored privately as the submission’s acknowledgement, each payee’s result is written on their record (only if their TIN still ends in the digits checked) and the submission becomes accepted. TINs are masked.\n\n#### Signature\n\n```http\nGET /business-made/efile/tinm/submissions/{id}/results (id: string) -> { id, status, env, ready, message?, summary: { total, matched, notMatched, byCode }, results: Result[], notSent }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `404` | EFILE_NOT_FOUND | E-file submission not found | No such record. | — |\n| `400` | TINM_NOT_TINM | This submission is not a TIN Matching request | Another program’s submission. | — |\n| `424` | SOR_NOT_AUTHORIZED | The IRS refused access to the e-Services mailbox (SOR) | The user cannot read the SOR mailbox. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/efile/iris/{taxYear}/prepare":{"post":{"operationId":"IrisController_prepare","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"taxYear","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"201":{"description":"{ submission, recordCount, errors[], warnings[], totals }","content":{"application/json":{"schema":{"type":"object","properties":{"submission":{"type":"object","additionalProperties":true},"recordCount":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"recordRef":{"type":"string","description":"bm_tax_form sk, or transmitter / issuer"}}}},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"recordRef":{"type":"string","description":"bm_tax_form sk, or transmitter / issuer"}}}},"totals":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"IRIS filing here supports 1099-NEC and 1099-MISC. — Another form type.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":400,"error":"IRIS filing here supports 1099-NEC and 1099-MISC.","path":"/business-made/efile/iris/{taxYear}/prepare","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"412":{"description":"IRIS filing is TEST only in this build — The IRIS connection is on production.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":412,"error":"IRIS filing is TEST only in this build","path":"/business-made/efile/iris/{taxYear}/prepare","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Prepare an IRIS 1099 transmission (build and validate only)","description":"Builds the IRIS A2A XML (Pub 5718, TY2025 schema 2.0.3) from the year’s bm_tax_form records of the form type (payee name/TIN/address from the signed W-9, else the contractor record; payer from the employer tax setup) and checks the IRS rules that can be checked locally: TINs, names, addresses, amounts, counts, TCC, Software ID, and the test-system rule that payer and payee TINs start with 000. The XML is stored privately on the open draft/ready submission for that year and form (created if there is none). Status becomes `ready`, or `draft` with `errors`. Body: { formType?: \"1099-NEC\" (default) | \"1099-MISC\", softwareId?, transmitter? }. Optional `transmitter` { tin, name, companyName, companyAddress { line1, line2, city, state, zip }, contactName, contactEmail, contactPhone } — the TCC holder’s details as on its IRIS TCC application. Defaults: the IRIS connection’s saved `transmitter`, else the payer (Issuer role). The TCC is always the IRIS connection’s. `softwareId` defaults to the server’s IRIS_SOFTWARE_ID.\n\n#### Signature\n\n```http\nPOST /business-made/efile/iris/{taxYear}/prepare (taxYear: number, body) -> { submission, recordCount, errors[], warnings[], totals }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `412` | IRIS_TEST_ONLY | IRIS filing is TEST only in this build | The IRIS connection is on production. | — |\n| `400` | IRIS_FORM_UNSUPPORTED | IRIS filing here supports 1099-NEC and 1099-MISC. | Another form type. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"formType":"1099-NEC"}}}}}},"/business-made/efile/iris/submissions/{id}/transmit":{"post":{"operationId":"IrisController_transmit","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_efile_submission sk (program iris)"}],"responses":{"201":{"description":"{ submission }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Already with the IRS — Submitted or accepted.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Already with the IRS","path":"/business-made/efile/iris/submissions/{id}/transmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"422":{"description":"The file has N error(s) to fix before it can be sent. — Local rules failed; `errors` lists them.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":422,"error":"The file has N error(s) to fix before it can be sent.","path":"/business-made/efile/iris/submissions/{id}/transmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"424":{"description":"IRIS is not enabled on this API client, or there is no IRIS TCC for it yet. — The IRS gateway refused the call (401/403 / scope).","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":424,"error":"IRIS is not enabled on this API client, or there is no IRIS TCC for it yet.","path":"/business-made/efile/iris/submissions/{id}/transmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Could not reach the IRIS test system — Network failure or timeout.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":502,"error":"Could not reach the IRIS test system","path":"/business-made/efile/iris/submissions/{id}/transmit","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}}},"tags":["Business Made · Government e-filing"],"summary":"Send an IRIS transmission to the IRS test system","description":"Rebuilds the file from current data with a new Unique Transmission ID (UUID:IRIS:TCC::A), stores it, gets the IRIS bearer token and POSTs it as multipart/form-data (`file`, text/xml) to the IRIS ATS intake endpoint. On success the submission is `submitted` with the Receipt ID and UTID, and the org’s `iris-acks` queue job is scheduled. Allowed from ready, draft (re-validated), error and rejected. Body: { softwareId?, transmitter? }.\n\n#### Signature\n\n```http\nPOST /business-made/efile/iris/submissions/{id}/transmit (id: string, body) -> { submission }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `422` | IRIS_VALIDATION_FAILED | The file has N error(s) to fix before it can be sent. | Local rules failed; `errors` lists them. | — |\n| `424` | IRIS_NOT_ENABLED | IRIS is not enabled on this API client, or there is no IRIS TCC for it yet. | The IRS gateway refused the call (401/403 / scope). | — |\n| `409` | IRIS_ALREADY_SENT | Already with the IRS | Submitted or accepted. | — |\n| `502` | IRIS_NETWORK | Could not reach the IRIS test system | Network failure or timeout. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/business-made/efile/iris/submissions/{id}/refresh":{"post":{"operationId":"IrisController_refresh","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_efile_submission sk (program iris)"}],"responses":{"201":{"description":"{ submission, final, irsStatus }","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Only a transmission that was sent to the IRS has an acknowledgement. — Not sent yet.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}},"example":{"statusCode":409,"error":"Only a transmission that was sent to the IRS has an acknowledgement.","path":"/business-made/efile/iris/submissions/{id}/refresh","method":"POST","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"Ask the IRS for the acknowledgement now","description":"POSTs a transStatusOrAckRequest (Receipt ID, else UTID; searchTypeCd A) to the IRIS ATS status endpoint. Accepted and Accepted with Errors → `accepted`, Partially Accepted → `partially_accepted`, Rejected → `rejected`; the ack is stored privately and each error is kept as { code, message, recordRef = bm_tax_form sk }. Processing leaves it `submitted`; Not Found for more than a day → `error`. The `iris-acks` queue job does the same on its own back-off.\n\n#### Signature\n\n```http\nPOST /business-made/efile/iris/submissions/{id}/refresh (id: string) -> { submission, final, irsStatus }\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\n| Status | Code | Message | When | What to do |\n| --- | --- | --- | --- | --- |\n| `409` | IRIS_NOT_SUBMITTED | Only a transmission that was sent to the IRS has an acknowledgement. | Not sent yet. | — |\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}},"/business-made/efile/iris/submissions/{id}/ack":{"get":{"operationId":"IrisController_ack","parameters":[{"name":"orgid","required":true,"in":"header","schema":{"type":"string"},"description":"Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials.","example":"acme-retail"},{"name":"id","required":true,"in":"path","schema":{"type":"string"},"description":"bm_efile_submission sk (program iris)"}],"responses":{"200":{"description":"Acknowledgement view","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"irsResult":{"type":"string","description":"Accepted | Accepted with Errors | Partially Accepted | Rejected | Not Found"},"receiptId":{"type":"string"},"utid":{"type":"string"},"recordCount":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"recordRef":{"type":"string","description":"bm_tax_form sk, or transmitter / issuer"}}}},"ackFileUrl":{"type":"string"},"submittedAt":{"type":"string"},"lastCheckedAt":{"type":"string"},"irsStatus":{"type":"string"},"checkError":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"tags":["Business Made · Government e-filing"],"summary":"The IRS acknowledgement of an IRIS transmission","description":"The acknowledgement as recorded (asks the IRS first while the transmission is still `submitted`). Never the raw ack (it can echo TINs): download it through the e-file file route.\n\n#### Signature\n\n```http\nGET /business-made/efile/iris/submissions/{id}/ack (id: string) -> Acknowledgement view\n```\n\n#### Access\n\nRequires a bearer JWT (`Authorization: Bearer <token>`).\n\n#### Errors\n\nPlus the standard platform errors: `401`, `403`, `429`, `500`."}}},"info":{"title":"AppEngine API Documentation","description":"Comprehensive API documentation for the AppEngine platform","version":"1.0","contact":{}},"tags":[{"name":"websitemint | appengine | appmint | thingrid","description":""},{"name":"CRM","description":"Customer Relationship Management endpoints"},{"name":"Storefront","description":"E-commerce and storefront management endpoints"},{"name":"Repository","description":"Data repository and file management endpoints"},{"name":"Users","description":"User management and authentication endpoints"},{"name":"Batch","description":"Batch processing endpoints"},{"name":"Chat","description":"Chat and messaging endpoints"},{"name":"Connect","description":"Integration and connection endpoints"},{"name":"Dynamic Query","description":"Dynamic data query endpoints"},{"name":"Sync","description":"Data synchronization endpoints"},{"name":"Upstream","description":"Upstream data flow endpoints"}],"servers":[],"components":{"securitySchemes":{"JWT-auth":{"scheme":"bearer","bearerFormat":"JWT","type":"http","name":"Authorization","description":"Enter JWT token","in":"header"},"orgid":{"type":"apiKey","in":"header","name":"orgid","description":"Organization ID"}},"schemas":{"WebsiteOverviewDto":{"type":"object","properties":{"totalVisitors":{"type":"number"},"pageViews":{"type":"number"},"sessions":{"type":"number"},"bounceRate":{"type":"number"},"avgSessionDuration":{"type":"number"},"avgPagesPerSession":{"type":"number"},"avgScrollDepth":{"type":"number"},"avgLoadTime":{"type":"number"},"botRate":{"type":"number"},"conversionRate":{"type":"number"}},"required":["totalVisitors","pageViews","sessions","bounceRate","avgSessionDuration","avgPagesPerSession","avgScrollDepth","avgLoadTime","botRate","conversionRate"]},"ChartDataPointDto":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"},"label":{"type":"string"}},"required":["date","value"]},"WebsiteAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/WebsiteOverviewDto"},"traffic":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"topPages":{"type":"array","items":{"type":"string"}},"exitPages":{"type":"array","items":{"type":"string"}},"deviceBreakdown":{"type":"array","items":{"type":"string"}},"browserBreakdown":{"type":"array","items":{"type":"string"}},"osBreakdown":{"type":"array","items":{"type":"string"}},"geographicData":{"type":"array","items":{"type":"string"}},"topCities":{"type":"array","items":{"type":"string"}},"trafficSources":{"type":"array","items":{"type":"string"}},"topReferrers":{"type":"array","items":{"type":"string"}},"utmAttribution":{"type":"object"},"eventBreakdown":{"type":"array","items":{"type":"string"}},"topSearches":{"type":"array","items":{"type":"string"}},"formAnalytics":{"type":"array","items":{"type":"string"}},"hourHeatmap":{"description":"24-entry hour-of-day traffic heatmap (UTC).","type":"array","items":{"type":"string"}},"dayOfWeekBreakdown":{"description":"7-entry day-of-week traffic breakdown. 1=Sun .. 7=Sat.","type":"array","items":{"type":"string"}},"sessionDurationBuckets":{"description":"Session duration distribution in buckets: <10s, 10-30s, 30-60s, 1-5m, 5-15m, 15m+","type":"array","items":{"type":"string"}},"pagesPerSessionBuckets":{"description":"Session depth distribution by pages per session: 1, 2-3, 4-10, 10+","type":"array","items":{"type":"string"}},"scrollDepthBuckets":{"description":"Scroll depth distribution: 0-25, 25-50, 50-75, 75-100","type":"array","items":{"type":"string"}},"newVsReturning":{"type":"object"},"languageBreakdown":{"type":"array","items":{"type":"string"}},"topIsps":{"type":"array","items":{"type":"string"}},"entryPages":{"type":"array","items":{"type":"string"}},"errorEvents":{"type":"array","items":{"type":"string"}},"siteBreakdown":{"type":"array","items":{"type":"string"}},"dailyMetrics":{"description":"Per-day metrics: sessions, unique visitors, bounce rate, avg duration.","type":"array","items":{"type":"string"}},"realTimeUsers":{"type":"number"}},"required":["overview","traffic","topPages","exitPages","deviceBreakdown","browserBreakdown","osBreakdown","geographicData","topCities","trafficSources","topReferrers","utmAttribution","eventBreakdown","topSearches","formAnalytics","hourHeatmap","dayOfWeekBreakdown","sessionDurationBuckets","pagesPerSessionBuckets","scrollDepthBuckets","newVsReturning","languageBreakdown","topIsps","entryPages","errorEvents","siteBreakdown","dailyMetrics","realTimeUsers"]},"BlogOverviewDto":{"type":"object","properties":{"totalPosts":{"type":"number"},"totalViews":{"type":"number"},"avgEngagement":{"type":"number"},"totalComments":{"type":"number"},"totalShares":{"type":"number"}},"required":["totalPosts","totalViews","avgEngagement","totalComments","totalShares"]},"BlogAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/BlogOverviewDto"},"topPosts":{"type":"array","items":{"type":"string"}},"engagementTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"categoryPerformance":{"type":"array","items":{"type":"string"}},"authorPerformance":{"type":"array","items":{"type":"string"}}},"required":["overview","topPosts","engagementTrends","categoryPerformance","authorPerformance"]},"WorkflowOverviewDto":{"type":"object","properties":{"totalTasks":{"type":"number"},"completedTasks":{"type":"number"},"overdueTasks":{"type":"number"},"avgCompletionTime":{"type":"number"},"teamProductivity":{"type":"number"}},"required":["totalTasks","completedTasks","overdueTasks","avgCompletionTime","teamProductivity"]},"WorkflowAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/WorkflowOverviewDto"},"tasksByStatus":{"type":"array","items":{"type":"string"}},"tasksByType":{"type":"array","items":{"type":"string"}},"assigneePerformance":{"type":"array","items":{"type":"string"}},"completionTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"workloadDistribution":{"type":"array","items":{"type":"string"}}},"required":["overview","tasksByStatus","tasksByType","assigneePerformance","completionTrends","workloadDistribution"]},"StorefrontOverviewDto":{"type":"object","properties":{"totalRevenue":{"type":"number"},"totalOrders":{"type":"number"},"avgOrderValue":{"type":"number"},"conversionRate":{"type":"number"},"returnCustomers":{"type":"number"},"pendingOrders":{"type":"number"}},"required":["totalRevenue","totalOrders","avgOrderValue","conversionRate","returnCustomers"]},"StorefrontAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/StorefrontOverviewDto"},"channelPerformance":{"type":"array","items":{"type":"string"}},"productPerformance":{"type":"array","items":{"type":"string"}},"salesTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"revenueTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"customerSegments":{"type":"array","items":{"type":"string"}}},"required":["overview","channelPerformance","productPerformance","salesTrends","customerSegments"]},"TicketOverviewDto":{"type":"object","properties":{"totalTickets":{"type":"number"},"openTickets":{"type":"number"},"resolvedTickets":{"type":"number"},"avgResolutionTime":{"type":"number"},"customerSatisfaction":{"type":"number"}},"required":["totalTickets","openTickets","resolvedTickets","avgResolutionTime","customerSatisfaction"]},"TicketAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/TicketOverviewDto"},"ticketsByPriority":{"type":"array","items":{"type":"string"}},"ticketsByCategory":{"type":"array","items":{"type":"string"}},"agentPerformance":{"type":"array","items":{"type":"string"}},"resolutionTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"satisfactionTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}}},"required":["overview","ticketsByPriority","ticketsByCategory","agentPerformance","resolutionTrends","satisfactionTrends"]},"LeadsOverviewDto":{"type":"object","properties":{"totalLeads":{"type":"number"},"qualifiedLeads":{"type":"number"},"conversionRate":{"type":"number"},"avgDealSize":{"type":"number"},"pipelineValue":{"type":"number"}},"required":["totalLeads","qualifiedLeads","conversionRate","avgDealSize","pipelineValue"]},"LeadsAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/LeadsOverviewDto"},"leadSources":{"type":"array","items":{"type":"string"}},"funnelData":{"type":"array","items":{"type":"string"}},"campaignPerformance":{"type":"array","items":{"type":"string"}},"leadTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"dealStages":{"type":"array","items":{"type":"string"}}},"required":["overview","leadSources","funnelData","campaignPerformance","leadTrends","dealStages"]},"AutomationOverviewDto":{"type":"object","properties":{"activeWorkflows":{"type":"number"},"totalExecutions":{"type":"number"},"successRate":{"type":"number"},"timeSaved":{"type":"number"},"errorRate":{"type":"number"}},"required":["activeWorkflows","totalExecutions","successRate","timeSaved","errorRate"]},"AutomationAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/AutomationOverviewDto"},"workflowPerformance":{"type":"array","items":{"type":"string"}},"triggerAnalysis":{"type":"array","items":{"type":"string"}},"executionTrends":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"errorAnalysis":{"type":"array","items":{"type":"string"}}},"required":["overview","workflowPerformance","triggerAnalysis","executionTrends","errorAnalysis"]},"UserAccountOverviewDto":{"type":"object","properties":{"totalUsers":{"type":"number"},"activeUsers":{"type":"number"},"totalRevenue":{"type":"number"},"avgRevenuePerUser":{"type":"number"},"churnRate":{"type":"number"}},"required":["totalUsers","activeUsers","totalRevenue","avgRevenuePerUser","churnRate"]},"UserAccountAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/UserAccountOverviewDto"},"userGrowth":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPointDto"}},"revenueBreakdown":{"type":"array","items":{"type":"string"}},"apiUsage":{"type":"array","items":{"type":"string"}},"userActivities":{"type":"array","items":{"type":"string"}},"costAnalysis":{"type":"array","items":{"type":"string"}},"subscriptionTiers":{"type":"array","items":{"type":"string"}}},"required":["overview","userGrowth","revenueBreakdown","apiUsage","userActivities","costAnalysis","subscriptionTiers"]},"EmailOverviewDto":{"type":"object","properties":{"totalSent":{"type":"number"},"delivered":{"type":"number"},"opened":{"type":"number"},"clicked":{"type":"number"},"replied":{"type":"number"},"bounced":{"type":"number"},"unsubscribed":{"type":"number"}},"required":["totalSent","delivered","opened","clicked","replied","bounced","unsubscribed"]},"EmailRatesDto":{"type":"object","properties":{"deliveryRate":{"type":"number"},"openRate":{"type":"number"},"clickRate":{"type":"number"},"replyRate":{"type":"number"},"bounceRate":{"type":"number"},"unsubscribeRate":{"type":"number"}},"required":["deliveryRate","openRate","clickRate","replyRate","bounceRate","unsubscribeRate"]},"EmailEngagementDto":{"type":"object","properties":{"avgTimeToOpen":{"type":"number"},"avgTimeToClick":{"type":"number"},"uniqueOpens":{"type":"number"},"uniqueClicks":{"type":"number"},"totalOpens":{"type":"number"},"totalClicks":{"type":"number"}},"required":["avgTimeToOpen","avgTimeToClick","uniqueOpens","uniqueClicks","totalOpens","totalClicks"]},"EmailPerformanceDto":{"type":"object","properties":{"topPerformingEmails":{"type":"array","items":{"type":"object","properties":{"subject":{"type":"string"},"openRate":{"type":"number"},"clickRate":{"type":"number"},"sentAt":{"type":"string"}}}},"bestSendTimes":{"type":"array","items":{"type":"object","properties":{"hour":{"type":"string"},"dayOfWeek":{"type":"string"},"openRate":{"type":"number"}}}}},"required":["topPerformingEmails","bestSendTimes"]},"EmailAnalyticsDto":{"type":"object","properties":{"overview":{"$ref":"#/components/schemas/EmailOverviewDto"},"rates":{"$ref":"#/components/schemas/EmailRatesDto"},"engagement":{"$ref":"#/components/schemas/EmailEngagementDto"},"performance":{"$ref":"#/components/schemas/EmailPerformanceDto"},"deviceBreakdown":{"type":"array","items":{"type":"object","properties":{"device":{"type":"string"},"percentage":{"type":"number"},"count":{"type":"number"}}}},"locationBreakdown":{"type":"array","items":{"type":"object","properties":{"location":{"type":"string"},"percentage":{"type":"number"},"count":{"type":"number"}}}},"campaignMetrics":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"sent":{"type":"number"},"opened":{"type":"number"},"clicked":{"type":"number"},"converted":{"type":"number"}}}}},"required":["overview","rates","engagement","performance","deviceBreakdown","locationBreakdown","campaignMetrics"]},"AdCreativeDto":{"type":"object","properties":{"headlines":{"description":"Headline variations","type":"array","items":{"type":"string"}},"descriptions":{"description":"Description variations","type":"array","items":{"type":"string"}},"cta":{"type":"string","description":"Call-to-action text"},"ctaType":{"type":"string","description":"CTA button type (platform-specific)"},"imageUrls":{"description":"Product images to use","type":"array","items":{"type":"string"}},"destinationUrl":{"type":"string","description":"Destination URL"},"validation":{"type":"object","description":"Character count validation"}},"required":["headlines","descriptions","cta","ctaType","imageUrls","destinationUrl"]},"PlatformCampaignPreviewDto":{"type":"object","properties":{"platform":{"type":"string","description":"Platform identifier"},"platformDisplayName":{"type":"string","description":"Platform display name"},"isEnabled":{"type":"boolean","description":"Is platform connected/enabled"},"connectionStatus":{"type":"string","description":"Connection status"},"allocatedBudget":{"type":"number","description":"Allocated budget for this platform"},"budgetPercentage":{"type":"number","description":"Budget percentage"},"adCreatives":{"description":"Generated ad creatives","allOf":[{"$ref":"#/components/schemas/AdCreativeDto"}]},"platformSettings":{"type":"object","description":"Platform-specific settings"},"recommendationScore":{"type":"number","description":"Recommendation score (0-100)"},"recommendationReason":{"type":"string","description":"Recommendation reason"}},"required":["platform","platformDisplayName","isEnabled","connectionStatus","allocatedBudget","budgetPercentage","adCreatives","recommendationScore","recommendationReason"]},"BudgetDistributionDto":{"type":"object","properties":{"totalBudget":{"type":"number","description":"Total budget"},"platformAllocations":{"description":"Per-platform allocation","type":"array","items":{"type":"object"}},"strategy":{"type":"string","description":"Distribution strategy used"},"hasHistoricalData":{"type":"boolean","description":"Historical data availability"}},"required":["totalBudget","platformAllocations","strategy","hasHistoricalData"]},"ProductSummaryDto":{"type":"object","properties":{"id":{"type":"string","description":"Product ID"},"name":{"type":"string","description":"Product name"},"price":{"type":"number","description":"Product price"},"imageUrl":{"type":"string","description":"Main image URL"},"category":{"type":"string","description":"Product category"}},"required":["id","name","price"]},"GenerationMetadataDto":{"type":"object","properties":{"aiModel":{"type":"string","description":"AI model used"},"generatedAt":{"type":"string","description":"Generation timestamp"},"durationMs":{"type":"number","description":"Generation duration in ms"},"tokenUsage":{"type":"object","description":"Token usage"}},"required":["aiModel","generatedAt","durationMs"]},"EstimatedPerformanceDto":{"type":"object","properties":{"estimatedReach":{"type":"object","description":"Estimated total reach"},"estimatedClicks":{"type":"object","description":"Estimated clicks"},"estimatedConversions":{"type":"object","description":"Estimated conversions"},"estimatedCpc":{"type":"number","description":"Estimated CPC"},"projectedRoas":{"type":"object","description":"Projected return on ad spend, from the promoted products' average price; null when prices are unknown"},"averageOrderValue":{"type":"number","description":"Average price of the promoted products, used for projectedRoas"},"confidenceLevel":{"type":"string","description":"Confidence level"},"estimateBasis":{"type":"string","description":"Basis for estimates"}},"required":["estimatedReach","estimatedClicks","estimatedConversions","estimatedCpc","projectedRoas","averageOrderValue","confidenceLevel","estimateBasis"]},"SellLikeMadPreviewResponseDto":{"type":"object","properties":{"previewId":{"type":"string","description":"Preview session ID for approval"},"platformCampaigns":{"description":"Generated campaigns per platform","type":"array","items":{"$ref":"#/components/schemas/PlatformCampaignPreviewDto"}},"budgetDistribution":{"description":"Budget distribution summary","allOf":[{"$ref":"#/components/schemas/BudgetDistributionDto"}]},"products":{"description":"Products included in campaign","type":"array","items":{"$ref":"#/components/schemas/ProductSummaryDto"}},"generationMetadata":{"description":"AI generation metadata","allOf":[{"$ref":"#/components/schemas/GenerationMetadataDto"}]},"estimatedPerformance":{"description":"Estimated reach and performance","allOf":[{"$ref":"#/components/schemas/EstimatedPerformanceDto"}]},"expiresAt":{"type":"string","description":"Preview expiration time"},"campaignName":{"type":"string","description":"Campaign name"}},"required":["previewId","platformCampaigns","budgetDistribution","products","generationMetadata","estimatedPerformance","expiresAt","campaignName"]},"PlatformLaunchResultDto":{"type":"object","properties":{"platform":{"type":"string","description":"Platform name"},"success":{"type":"boolean","description":"Success status"},"campaignId":{"type":"string","description":"Platform-specific campaign ID"},"error":{"type":"string","description":"Error message if failed"}},"required":["platform","success"]},"LaunchResultDto":{"type":"object","properties":{"success":{"type":"boolean","description":"Overall success status"},"masterCampaignId":{"type":"string","description":"Master campaign ID"},"platformResults":{"description":"Per-platform launch results","type":"array","items":{"$ref":"#/components/schemas/PlatformLaunchResultDto"}},"launchedAt":{"type":"string","description":"Launch timestamp"},"message":{"type":"string","description":"Summary message"}},"required":["success","masterCampaignId","platformResults","launchedAt","message"]},"AvailablePlatformDto":{"type":"object","properties":{"platform":{"type":"string","description":"Platform identifier"},"displayName":{"type":"string","description":"Platform display name"},"connected":{"type":"boolean","description":"Is connected"},"accountId":{"type":"string","description":"Account ID if connected"},"status":{"type":"string","description":"Status"},"message":{"type":"string","description":"Error or status message explaining the current state"}},"required":["platform","displayName","connected","status"]},"ErrorEnvelope":{"type":"object","required":["statusCode","error","path","method","timeStamp"],"additionalProperties":true,"properties":{"statusCode":{"type":"integer","description":"HTTP status code, repeated in the body.","example":404},"error":{"type":"string","description":"Human-readable failure description.","example":"Category not found"},"path":{"type":"string","description":"Request path that failed, including query string.","example":"/storefront/categories"},"method":{"type":"string","description":"HTTP method of the failed request.","example":"POST"},"timeStamp":{"type":"string","format":"date-time","description":"Server time the error was produced.","example":"2026-08-29T14:03:22.118Z"}}}},"responses":{"Unauthenticated":{"description":"Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"statusCode":401,"error":"Authentication failed: Invalid or expired token","path":"/example","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"Forbidden":{"description":"You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"statusCode":403,"error":"You do not have permission to perform this action","path":"/example","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}},"RateLimited":{"description":"Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets.","content":{"application/json":{"schema":{"type":"object","properties":{"statusCode":{"type":"integer","example":429},"error":{"type":"string","example":"Too Many Requests"},"message":{"type":"string","example":"Too many requests from this IP, please try again later"}}},"example":{"statusCode":429,"error":"Too Many Requests","message":"Too many requests from this IP, please try again later"}}}},"ServerError":{"description":"An unexpected error occurred. Our team has been notified. — An unhandled server-side failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"statusCode":500,"error":"An unexpected error occurred. Our team has been notified.","path":"/example","method":"GET","timeStamp":"2026-08-29T14:03:22.118Z"}}}}}}}